diff options
Diffstat (limited to 'users')
1464 files changed, 235143 insertions, 0 deletions
diff --git a/users/Profpatsch/OWNERS b/users/Profpatsch/OWNERS new file mode 100644 index 000000000000..5a73d4c3a1fd --- /dev/null +++ b/users/Profpatsch/OWNERS @@ -0,0 +1,4 @@ +inherited: false +owners: + - Profpatsch + - sterni diff --git a/users/Profpatsch/advent-of-code/2020/01/main.py b/users/Profpatsch/advent-of-code/2020/01/main.py new file mode 100644 index 000000000000..e636017a54d5 --- /dev/null +++ b/users/Profpatsch/advent-of-code/2020/01/main.py @@ -0,0 +1,22 @@ +import sys + +l = [] +with open('./input', 'r') as f: + for line in f: + l.append(int(line)) + +s = set(l) + +res=None +for el in s: + for el2 in s: + if (2020-(el+el2)) in s: + res=(el, el2, 2020-(el+el2)) + break + +if res == None: + sys.exit("could not find a number that adds to 2020") + +print(res) + +print(res[0] * res[1] * res[2]) diff --git a/users/Profpatsch/advent-of-code/2020/02/main.py b/users/Profpatsch/advent-of-code/2020/02/main.py new file mode 100644 index 000000000000..e3b27c382a21 --- /dev/null +++ b/users/Profpatsch/advent-of-code/2020/02/main.py @@ -0,0 +1,77 @@ +import sys + +def parse(line): + a = line.split(sep=" ", maxsplit=1) + assert len(a) == 2 + fromto = a[0].split(sep="-") + assert len(fromto) == 2 + (from_, to) = (int(fromto[0]), int(fromto[1])) + charpass = a[1].split(sep=": ") + assert len(charpass) == 2 + char = charpass[0] + assert len(char) == 1 + pass_ = charpass[1] + assert pass_.endswith("\n") + pass_ = pass_[:-1] + return { + "from": from_, + "to": to, + "char": char, + "pass": pass_ + } + +def char_in_pass(char, pass_): + return pass_.count(char) + +def validate_01(entry): + no = char_in_pass(entry["char"], entry["pass"]) + if no < entry["from"]: + return { "too-small": entry } + elif no > entry["to"]: + return { "too-big": entry } + else: + return { "ok": entry } + +def char_at_pos(char, pos, pass_): + assert pos <= len(pass_) + return pass_[pos-1] == char + +def validate_02(entry): + one = char_at_pos(entry["char"], entry["from"], entry["pass"]) + two = char_at_pos(entry["char"], entry["to"], entry["pass"]) + if one and two: + return { "both": entry } + elif one: + return { "one": entry } + elif two: + return { "two": entry } + else: + return { "none": entry } + + +res01 = [] +res02 = [] +with open("./input", 'r') as f: + for line in f: + p = parse(line) + res01.append(validate_01(p)) + res02.append(validate_02(p)) + +count01=0 +for r in res01: + print(r) + if r.get("ok", False): + count01=count01+1 + +count02=0 +for r in res02: + print(r) + if r.get("one", False): + count02=count02+1 + elif r.get("two", False): + count02=count02+1 + else: + pass + +print("count 1: {}".format(count01)) +print("count 2: {}".format(count02)) diff --git a/users/Profpatsch/advent-of-code/2020/03/main.py b/users/Profpatsch/advent-of-code/2020/03/main.py new file mode 100644 index 000000000000..4d6baf946c3e --- /dev/null +++ b/users/Profpatsch/advent-of-code/2020/03/main.py @@ -0,0 +1,66 @@ +import itertools +import math + +def tree_line(init): + return { + "init-len": len(init), + "known": '', + "rest": itertools.repeat(init) + } + +def tree_line_at(pos, tree_line): + needed = (pos + 1) - len(tree_line["known"]) + # internally advance the tree line to the position requested + if needed > 0: + tree_line["known"] = tree_line["known"] \ + + ''.join( + itertools.islice( + tree_line["rest"], + 1+math.floor(needed / tree_line["init-len"]))) + # print(tree_line) + return tree_line["known"][pos] == '#' + +def tree_at(linepos, pos, trees): + return tree_line_at(pos, trees[linepos]) + +def slope_positions(trees, right, down): + line = 0 + pos = 0 + while line < len(trees): + yield (line, pos) + line = line + down + pos = pos + right + +trees = [] +with open("./input", 'r') as f: + for line in f: + line = line.rstrip() + trees.append(tree_line(line)) + +# print(list(itertools.islice(trees[0], 5))) +# print(list(map( +# lambda x: tree_at(0, x, trees), +# range(100) +# ))) +# print(list(slope_positions(trees, right=3, down=1))) + +def count_slope_positions(trees, slope): + count = 0 + for (line, pos) in slope: + if tree_at(line, pos, trees): + count = count + 1 + return count + +print( + count_slope_positions(trees, slope_positions(trees, right=1, down=1)) + * + count_slope_positions(trees, slope_positions(trees, right=3, down=1)) + * + count_slope_positions(trees, slope_positions(trees, right=5, down=1)) + * + count_slope_positions(trees, slope_positions(trees, right=7, down=1)) + * + count_slope_positions(trees, slope_positions(trees, right=1, down=2)) +) + +# I realized I could have just used a modulo instead โฆ diff --git a/users/Profpatsch/advent-of-code/2020/04/main.py b/users/Profpatsch/advent-of-code/2020/04/main.py new file mode 100644 index 000000000000..36bbed7146d6 --- /dev/null +++ b/users/Profpatsch/advent-of-code/2020/04/main.py @@ -0,0 +1,104 @@ +import sys +import itertools +import re +import pprint + +def get_entry(fd): + def to_dict(keyval): + res = {} + for (k, v) in keyval: + assert k not in res + res[k] = v + return res + + res = [] + for line in fd: + if line == "\n": + yield to_dict(res) + res = [] + else: + line = line.rstrip() + items = line.split(" ") + for i in items: + res.append(i.split(":", maxsplit=2)) + +def val_hgt(hgt): + m = re.fullmatch(r'([0-9]+)(cm|in)', hgt) + if m: + (i, what) = m.group(1,2) + i = int(i) + if what == "cm": + return i >= 150 and i <= 193 + elif what == "in": + return i >= 59 and i <= 76 + else: + return False + +required_fields = [ + { "name": "byr", + "check": lambda s: int(s) >= 1920 and int(s) <= 2002 + }, + { "name": "iyr", + "check": lambda s: int(s) >= 2010 and int(s) <= 2020 + }, + { "name": "eyr", + "check": lambda s: int(s) >= 2020 and int(s) <= 2030, + }, + { "name": "hgt", + "check": lambda s: val_hgt(s) + }, + { "name": "hcl", + "check": lambda s: re.fullmatch(r'#[0-9a-f]{6}', s) + }, + { "name": "ecl", + "check": lambda s: re.fullmatch(r'amb|blu|brn|gry|grn|hzl|oth', s) + }, + { "name": "pid", + "check": lambda s: re.fullmatch(r'[0-9]{9}', s) + }, + # we should treat it as not required + # "cid" +] + +required_dict = {} +for f in required_fields: + required_dict[f["name"]] = f + +def validate(keyval): + if keyval[0] not in required_dict: + return { "ok": keyval } + if required_dict[keyval[0]]["check"](keyval[1]): + return { "ok": keyval } + else: + return { "validation": keyval } + +def all_fields(entry): + missing = [] + for r in required_dict: + if r not in e: + missing.append(r) + if missing == []: + return { "ok": entry } + else: + return { "missing": missing } + +count=0 +for e in get_entry(sys.stdin): + a = all_fields(e) + if a.get("ok", False): + res = {} + bad = False + for keyval in e.items(): + r = validate(keyval) + if r.get("validation", False): + bad = True + res[keyval[0]] = r + if bad: + pprint.pprint({ "validation": res }) + else: + pprint.pprint({ "ok": e }) + count = count+1 + else: + pprint.pprint(a) + +print(count) diff --git a/users/Profpatsch/arglib/netencode.nix b/users/Profpatsch/arglib/netencode.nix new file mode 100644 index 000000000000..50f4c11c2d8f --- /dev/null +++ b/users/Profpatsch/arglib/netencode.nix @@ -0,0 +1,40 @@ +{ depot, pkgs, lib, ... }: + +let + netencode = { + rust = depot.nix.writers.rustSimpleLib { + name = "arglib-netencode"; + dependencies = [ + depot.users.Profpatsch.execline.exec-helpers + depot.users.Profpatsch.netencode.netencode-rs + ]; + } '' + extern crate netencode; + extern crate exec_helpers; + + use netencode::{T}; + use std::os::unix::ffi::OsStrExt; + + pub fn arglib_netencode(prog_name: &str, env: Option<&std::ffi::OsStr>) -> T { + let env = match env { + None => std::ffi::OsStr::from_bytes("ARGLIB_NETENCODE".as_bytes()), + Some(a) => a + }; + let t = match std::env::var_os(env) { + None => exec_helpers::die_user_error(prog_name, format!("could not read args, envvar {} not set", env.to_string_lossy())), + // TODO: good error handling for the different parser errors + Some(soup) => match netencode::parse::t_t(soup.as_bytes()) { + Ok((remainder, t)) => match remainder.is_empty() { + true => t, + false => exec_helpers::die_environment_problem(prog_name, format!("arglib: there was some unparsed bytes remaining: {:?}", remainder)) + }, + Err(err) => exec_helpers::die_environment_problem(prog_name, format!("arglib parsing error: {:?}", err)) + } + }; + std::env::remove_var(env); + t + } + ''; + }; + +in depot.nix.readTree.drvTargets netencode diff --git a/users/Profpatsch/atomically-write.nix b/users/Profpatsch/atomically-write.nix new file mode 100644 index 000000000000..d5039d3e46b9 --- /dev/null +++ b/users/Profpatsch/atomically-write.nix @@ -0,0 +1,28 @@ +{ depot, pkgs, ... }: +# Atomically write a file (just `>` redirection in bash +# empties a file even if the command crashes). +# +# Maybe there is an existing tool for that? +# But itโs easy enough to implement. +# +# Example: +# atomically-write +# ./to +# echo "foo" +# +# will atomically write the string "foo" into ./to +let + atomically-write = pkgs.writers.writeDash "atomically-write" '' + set -e + to=$1 + shift + # assumes that the tempfile is on the same file system, (or in memory) + # for the `mv` at the end to be more-or-less atomic. + tmp=$(${pkgs.coreutils}/bin/mktemp -d) + trap 'rm -r "$tmp"' EXIT + "$@" \ + > "$tmp/out" + mv "$tmp/out" "$to" + ''; + +in atomically-write diff --git a/users/Profpatsch/blog/default.nix b/users/Profpatsch/blog/default.nix new file mode 100644 index 000000000000..6ac3c3eb5104 --- /dev/null +++ b/users/Profpatsch/blog/default.nix @@ -0,0 +1,373 @@ +{ depot, pkgs, lib, ... }: + +let + bins = depot.nix.getBins pkgs.lowdown [ "lowdown" ] + // depot.nix.getBins pkgs.cdb [ "cdbget" "cdbmake" "cdbdump" ] + // depot.nix.getBins pkgs.coreutils [ "mv" "cat" "printf" "test" ] + // depot.nix.getBins pkgs.s6-networking [ "s6-tcpserver" ] + // depot.nix.getBins pkgs.time [ "time" ] + ; + + # / + # TODO: use + toplevel = [ + { + route = [ "notes" ]; + name = "Notes"; + page = {cssFile}: router cssFile; + } + { + route = [ "projects" ]; + name = "Projects"; + # page = projects; + } + ]; + + # /notes/* + notes = [ + { + route = [ "notes" "preventing-oom" ]; + name = "Preventing out-of-memory (OOM) errors on Linux"; + page = {cssFile}: markdownToHtml { + name = "preventing-oom"; + markdown = ./notes/preventing-oom.md; + inherit cssFile; + }; + } + { + route = [ "notes" "rust-string-conversions" ]; + name = "Converting between different String types in Rust"; + page = {cssFile}: markdownToHtml { + name = "rust-string-conversions"; + markdown = ./notes/rust-string-conversions.md; + inherit cssFile; + }; + } + ]; + + projects = [ + { + name = "lorri"; + description = "<code>nix-shell</code> replacement for projects"; + link = "https://github.com/nix-community/lorri"; + } + { + name = "netencode"; + description = ''A human-readble nested data exchange format inspired by <a href="https://en.wikipedia.org/wiki/Netstring">netstrings</a> and <a href="https://en.wikipedia.org/wiki/Bencode">bencode</a>.''; + link = depotCgitLink { relativePath = "users/Profpatsch/netencode/README.md"; }; + } + { + name = "yarn2nix"; + description = ''nix dependency generator for the <a href="https://yarnpkg.com/"><code>yarn</code> Javascript package manager</a>''; + link = "https://github.com/Profpatsch/yarn2nix"; + } + ]; + + posts = [ + { + date = "2017-05-04"; + title = "Ligature Emulation in Emacs"; + subtitle = "Itโs not pretty, but the results are"; + description = "How to set up ligatures using <code>prettify-symbols-mode</code> and the Hasklig/FiraCode fonts."; + page = {cssFile}: markdownToHtml { + name = "2017-05-04-ligature-emluation-in-emacs"; + markdown = ./posts/2017-05-04-ligature-emulation-in-emacs.md; + inherit cssFile; + }; + route = [ "posts" "2017-05-04-ligature-emluation-in-emacs" ]; + tags = ["emacs"]; + } + ]; + + # convert a markdown file to html via lowdown + markdownToHtml = { + name, + # the file to convert + markdown, + # css file to add to the final result, as { route } + cssFile + }: + depot.nix.runExecline "${name}.html" {} ([ + "importas" "out" "out" + (depot.users.Profpatsch.lib.debugExec "") + bins.lowdown + "-s" "-Thtml" + ] ++ + (lib.optional (cssFile != null) (["-M" "css=${mkRoute cssFile.route}"])) + ++ [ + "-o" "$out" + markdown + ]); + + # takes a { route โฆ } attrset and converts the route lists to an absolute path + fullRoute = attrs: lib.pipe attrs [ + (map (x@{route, ...}: x // { route = mkRoute route; })) + ]; + + # a cdb from route to a netencoded version of data for each route + router = cssFile: lib.pipe (notes ++ posts) [ + (map (r: with depot.users.Profpatsch.lens; + lib.pipe r [ + (over (field "route") mkRoute) + (over (field "page") (_ { inherit cssFile; })) + ])) + (map (x: { + name = x.route; + value = depot.users.Profpatsch.netencode.gen.dwim x; + })) + lib.listToAttrs + (cdbMake "router") + ]; + + # Create a link to the given source file/directory, given the relative path in the depot repo. + # Checks that the file exists at evaluation time. + depotCgitLink = { + # relative path from the depot root (without leading /). + relativePath + }: + assert + (lib.assertMsg + (builtins.pathExists (depot.path + "/" + relativePath)) + "depotCgitLink: path /${relativePath} does not exist in depot"); + "https://code.tvl.fyi/tree/${relativePath}"; + + # look up a route by path ($1) + router-lookup = cssFile: depot.nix.writeExecline "router-lookup" { readNArgs = 1; } [ + cdbLookup (router cssFile) "$1" + ]; + + runExeclineStdout = name: args: cmd: depot.nix.runExecline name args ([ + "importas" "-ui" "out" "out" + "redirfd" "-w" "1" "$out" + ] ++ cmd); + + notes-index-html = + let o = fullRoute notes; + in '' + <ul> + ${scope o (o: '' + <li><a href="${str o.route}">${esc o.name}</a></li> + '')} + </ul> + ''; + + notes-index = pkgs.writeText "notes-index.html" notes-index-html; + + # A simple mustache-inspired string interpolation combinator + # that takes an object and a template (a function from o to string) + # and returns a string. + scope = o: tpl: + if builtins.typeOf o == "list" then + lib.concatMapStringsSep "\n" tpl o + else if builtins.typeOf o == "set" then + tpl o + else throw "${lib.generators.toPretty {} o} not allowed in template"; + + # string-escape html (TODO) + str = s: s; + # html-escape (TODO) + esc = s: s; + html = s: s; + + projects-index-html = + let o = projects; + in '' + <dl> + ${scope o (o: '' + <dt><a href="${str o.link}">${esc o.name}</a></dt> + <dd>${html o.description}</dd> + '')} + </dl> + ''; + + projects-index = pkgs.writeText "projects-index.html" projects-index-html; + + posts-index-html = + let o = fullRoute posts; + in '' + <dl> + ${scope o (o: '' + <dt>${str o.date} <a href="${str o.route}">${esc o.title}</a></dt> + <dd>${html o.description}</dd> + '')} + </dl> + ''; + + posts-index = pkgs.writeText "projects-index.html" posts-index-html; + + arglibNetencode = val: depot.nix.writeExecline "arglib-netencode" { } [ + "export" "ARGLIB_NETENCODE" (depot.users.Profpatsch.netencode.gen.dwim val) + "$@" + ]; + + # A simple http server that serves the site. Yes, itโs horrible. + site-server = { cssFile, port }: depot.nix.writeExecline "blog-server" {} [ + (depot.users.Profpatsch.lib.runInEmptyEnv [ "PATH" ]) + bins.s6-tcpserver "127.0.0.1" port + bins.time "--format=time: %es" "--" + runOr return400 + "pipeline" [ + (arglibNetencode { + what = "request"; + }) + depot.users.Profpatsch.read-http + ] + depot.users.Profpatsch.netencode.record-splice-env + runOr return500 + "importas" "-i" "path" "path" + "if" [ depot.tools.eprintf "GET \${path}\n" ] + runOr return404 + "backtick" "-ni" "TEMPLATE_DATA" [ + # TODO: factor this out of here, this is routing not serving + "ifelse" [ bins.test "$path" "=" "/notes" ] + [ "export" "content-type" "text/html" + "export" "serve-file" notes-index + depot.users.Profpatsch.netencode.env-splice-record + ] + "ifelse" [ bins.test "$path" "=" "/projects" ] + [ "export" "content-type" "text/html" + "export" "serve-file" projects-index + depot.users.Profpatsch.netencode.env-splice-record + ] + "ifelse" [ bins.test "$path" "=" "/posts" ] + [ "export" "content-type" "text/html" + "export" "serve-file" posts-index + depot.users.Profpatsch.netencode.env-splice-record + ] + # TODO: ignore potential query arguments. See 404 message + "pipeline" [ (router-lookup cssFile) "$path" ] + depot.users.Profpatsch.netencode.record-splice-env + "importas" "-ui" "page" "page" + "export" "content-type" "text/html" + "export" "serve-file" "$page" + depot.users.Profpatsch.netencode.env-splice-record + ] + runOr return500 + "if" [ + "pipeline" [ bins.printf '' + HTTP/1.1 200 OK + Content-Type: {{{content-type}}}; charset=UTF-8 + Connection: close + + '' ] + depot.users.Profpatsch.netencode.netencode-mustache + ] + "pipeline" [ "importas" "t" "TEMPLATE_DATA" bins.printf "%s" "$t" ] + depot.users.Profpatsch.netencode.record-splice-env + "importas" "-ui" "serve-file" "serve-file" + bins.cat "$serve-file" + ]; + + # run argv or $1 if argv returns a failure status code. + runOr = depot.nix.writeExecline "run-or" { readNArgs = 1; } [ + "foreground" [ "$@" ] + "importas" "?" "?" + "ifelse" [ bins.test "$?" "-eq" "0" ] + [] + "if" [ depot.tools.eprintf "runOr: exited \${?}, running \${1}\n" ] + "$1" + ]; + + return400 = depot.nix.writeExecline "return400" {} [ + bins.printf "%s" '' + HTTP/1.1 400 Bad Request + Content-Type: text/plain; charset=UTF-8 + Connection: close + + '' + ]; + + return404 = depot.nix.writeExecline "return404" {} [ + bins.printf "%s" '' + HTTP/1.1 404 Not Found + Content-Type: text/plain; charset=UTF-8 + Connection: close + + This page doesnโt exist! Query arguments are not handled at the moment. + '' + ]; + + return500 = depot.nix.writeExecline "return500" {} [ + bins.printf "%s" '' + HTTP/1.1 500 Internal Server Error + Content-Type: text/plain; charset=UTF-8 + Connection: close + + Encountered an internal server error. Please try again. + '' + ]; + + capture-stdin = depot.nix.writers.rustSimple { + name = "capture-stdin"; + dependencies = [ depot.users.Profpatsch.execline.exec-helpers ]; + } '' + extern crate exec_helpers; + use std::io::Read; + fn main() { + let (args, prog) = exec_helpers::args_for_exec("capture-stdin", 1); + let valname = &args[1]; + let mut v : Vec<u8> = vec![]; + std::io::stdin().lock().read_to_end(&mut v).unwrap(); + exec_helpers::exec_into_args("capture-stdin", prog, vec![(valname, v)]); + } + ''; + + # go from a list of path elements to an absolute route string + mkRoute = route: "/" + lib.concatMapStringsSep "/" urlencodeAscii route; + + # urlencodes, but only ASCII characters + # https://en.wikipedia.org/wiki/Percent-encoding + urlencodeAscii = urlPiece: + let + raw = [ "!" "#" "$" "%" "&" "'" "(" ")" "*" "+" "," "/" ":" ";" "=" "?" "@" "[" "]" ]; + enc = [ "%21" "%23" "%24" "%25" "%26" "%27" "%28" "%29" "%2A" "%2B" "%2C" "%2F" "%3A" "%3B" "%3D" "%3F" "%40" "%5B" "%5D" ]; + rest = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ]; + in + assert lib.assertMsg (lib.all (c: builtins.elem c (raw ++ rest)) (lib.stringToCharacters urlPiece)) + "urlencodeAscii: the urlPiece must only contain valid url ASCII characters, was: ${urlPiece}"; + builtins.replaceStrings raw enc urlPiece; + + + # create a cdb record entry, as required by the cdbmake tool + cdbRecord = key: val: + "+${toString (builtins.stringLength key)},${toString (builtins.stringLength val)}:" + + "${key}->${val}\n"; + + # create a full cdbmake input from an attribute set of keys to values (strings) + cdbRecords = + with depot.nix.yants; + defun [ (attrs (either drv string)) string ] + (attrs: + (lib.concatStrings (lib.mapAttrsToList cdbRecord attrs)) + "\n"); + + # run cdbmake on a list of key/value pairs (strings + cdbMake = name: attrs: depot.nix.runExecline "${name}.cdb" { + stdin = cdbRecords attrs; + } [ + "importas" "out" "out" + depot.users.Profpatsch.lib.eprint-stdin + "if" [ bins.cdbmake "db" "tmp" ] + bins.mv "db" "$out" + ]; + + # look up a key ($2) in the given cdb ($1) + cdbLookup = depot.nix.writeExecline "cdb-lookup" { readNArgs = 2; } [ + # cdb ($1) on stdin + "redirfd" "-r" "0" "$1" + # key ($2) lookup + bins.cdbget "$2" + ]; + +in depot.nix.readTree.drvTargets { + inherit + router + depotCgitLink + site-server + notes-index + notes-index-html + projects-index + projects-index-html + posts-index-html + ; + +} diff --git a/users/Profpatsch/blog/notes/preventing-oom.md b/users/Profpatsch/blog/notes/preventing-oom.md new file mode 100644 index 000000000000..59ea4f747700 --- /dev/null +++ b/users/Profpatsch/blog/notes/preventing-oom.md @@ -0,0 +1,33 @@ +tags: linux +date: 2020-01-25 +certainty: likely +status: initial +title: Preventing out-of-memory (OOM) errors on Linux + +# Preventing out-of-memory (OOM) errors on Linux + +Iโve been running out of memory more and more often lately. I donโt use any swap space because I am of the opinion that 16GB of memory should be sufficient for most daily and professional tasks. Which is generally true, however sometimes I have a runaway filling my memory. Emacs is very good at doing this for example, prone to filling your RAM when you open json files with very long lines. + +In theory, the kernel OOM killer should come in and save the day, but the Linux OOM killer is notorious for being extremely โฆ conservative. It will try to free every internal structure it can before even thinking about touching any userspace processes. At that point, the desktop usually stopped responding minutes ago. + +Luckily the kernel provides memory statistics for the whole system, as well as single process, and the [`earlyoom`](https://github.com/rfjakob/earlyoom) tool uses those to keep memory usage under a certain limit. It will start killing processes, โheaviestโ first, until the given upper memory limit is satisfied again. + +On NixOS, I set: + +```nix +{ + services.earlyoom = { + enable = true; + freeMemThreshold = 5; # <%5 free + }; +} +``` + +and after activation, this simple test shows whether the daemon is working: + +```shell +$ tail /dev/zero +fish: โtail /dev/zeroโ terminated by signal SIGTERM (Polite quit request) +``` + +`tail /dev/zero` searches for the last line of the file `/dev/zero`, and since it cannot know that there is no next line and no end to the stream of `\0` this file produces, it will fill the RAM as quickly as physically possible. Before it can fill it completely, `earlyoom` recognizes that the limit was breached, singles out the `tail` command as the process using the most amount of memory, and sends it a `SIGTERM`. diff --git a/users/Profpatsch/blog/notes/rust-string-conversions.md b/users/Profpatsch/blog/notes/rust-string-conversions.md new file mode 100644 index 000000000000..99071ef9d370 --- /dev/null +++ b/users/Profpatsch/blog/notes/rust-string-conversions.md @@ -0,0 +1,53 @@ +# Converting between different String types in Rust + +``` +let s: String = ... +let st: &str = ... +let u: &[u8] = ... +let b: [u8; 3] = b"foo" +let v: Vec<u8> = ... +let os: OsString = ... +let ost: OsStr = ... + +From To Use Comment +---- -- --- ------- +&str -> String String::from(st) +&str -> &[u8] st.as_bytes() +&str -> Vec<u8> st.as_bytes().to_owned() via &[u8] +&str -> &OsStr OsStr::new(st) + +String -> &str &s alt. s.as_str() +String -> &[u8] s.as_bytes() +String -> Vec<u8> s.into_bytes() +String -> OsString OsString::from(s) + +&[u8] -> &str str::from_utf8(u).unwrap() +&[u8] -> String String::from_utf8(u).unwrap() +&[u8] -> Vec<u8> u.to_owned() +&[u8] -> &OsStr OsStr::from_bytes(u) use std::os::unix::ffi::OsStrExt; + +[u8; 3] -> &[u8] &b[..] byte literal +[u8; 3] -> &[u8] "foo".as_bytes() alternative via utf8 literal + +Vec<u8> -> &str str::from_utf8(&v).unwrap() via &[u8] +Vec<u8> -> String String::from_utf8(v) +Vec<u8> -> &[u8] &v +Vec<u8> -> OsString OsString::from_vec(v) use std::os::unix::ffi::OsStringExt; + +&OsStr -> &str ost.to_str().unwrap() +&OsStr -> String ost.to_os_string().into_string() via OsString + .unwrap() +&OsStr -> Cow<str> ost.to_string_lossy() Unicode replacement characters +&OsStr -> OsString ost.to_os_string() +&OsStr -> &[u8] ost.as_bytes() use std::os::unix::ffi::OsStringExt; + +OsString -> String os.into_string().unwrap() returns original OsString on failure +OsString -> &str os.to_str().unwrap() +OsString -> &OsStr os.as_os_str() +OsString -> Vec<u8> os.into_vec() use std::os::unix::ffi::OsStringExt; +``` + + +## Source + +Original source is [this document on Pastebin](https://web.archive.org/web/20190710121935/https://pastebin.com/Mhfc6b9i) diff --git a/users/Profpatsch/blog/posts/2017-05-04-ligature-emulation-in-emacs.md b/users/Profpatsch/blog/posts/2017-05-04-ligature-emulation-in-emacs.md new file mode 100644 index 000000000000..ba80888badd8 --- /dev/null +++ b/users/Profpatsch/blog/posts/2017-05-04-ligature-emulation-in-emacs.md @@ -0,0 +1,123 @@ +title: Ligature Emulation in Emacs +date: 2017-05-04 + +Monday was (yet another) +[NixOS hackathon][hackathon] at [OpenLab Augsburg][ola]. +[Maximilian][mhuber] was there and to my amazement +he got working ligatures in his Haskell files in Emacs! Ever since Hasklig +updated its format to use ligatures and private Unicode code points a while ago, +the hack I had used in my config stopped working. + +Encouraged by that I decided to take a look on Tuesday. Long story short, I was +able to [get it working in a pretty satisfying way][done]. + +[hackathon]: https://www.meetup.com/Munich-NixOS-Meetup/events/239077247/ +[mhuber]: https://github.com/maximilianhuber +[ola]: https://openlab-augsburg.de +[done]: https://github.com/i-tu/Hasklig/issues/84#issuecomment-298803495 + +Whatโs left to do is package it into a module and push to melpa. + + +### elisp still sucks, but itโs bearable, sometimes + +Iโm the kind of person who, when trying to fix something elisp related, normally +gives up two hours later and three macro calls deep. Yes, homoiconic, +non-lexically-scoped, self-rewriting code is not exactly my fetish. +This time the task and the library (`prettify-symbols-mode`) were simple enough +for that to not happen. + +Some interesting technical trivia: + +- elisp literal character syntax is `?c`. `?\t` is the tab character +- You join characters by `(string c1 c2 c3 ...)` +- [dash.el][dash] is pretty awesome and does what a functional programmer + expects. Also, Rainbow Dash. +- Hasklig and FiraCode multi-column symbols actually [only occupy one column, on + the far right of the glyph][glyph]. `my-correct-symbol-bounds` fixes emacsโ + rendering in that case. + + +[dash]: https://github.com/magnars/dash.el +[glyph]: https://github.com/tonsky/FiraCode/issues/211#issuecomment-239082368 + + +## Appendix A + +For reference, hereโs the complete code as it stands now. Feel free to paste +into your config; letโs make it [MIT][mit]. Maybe link to this site, in case there are +updates. + +[mit]: https://opensource.org/licenses/MIT + +```elisp + (defun my-correct-symbol-bounds (pretty-alist) + "Prepend a TAB character to each symbol in this alist, +this way compose-region called by prettify-symbols-mode +will use the correct width of the symbols +instead of the width measured by char-width." + (mapcar (lambda (el) + (setcdr el (string ?\t (cdr el))) + el) + pretty-alist)) + + (defun my-ligature-list (ligatures codepoint-start) + "Create an alist of strings to replace with +codepoints starting from codepoint-start." + (let ((codepoints (-iterate '1+ codepoint-start (length ligatures)))) + (-zip-pair ligatures codepoints))) + + ; list can be found at https://github.com/i-tu/Hasklig/blob/master/GlyphOrderAndAliasDB#L1588 + (setq my-hasklig-ligatures + (let* ((ligs '("&&" "***" "*>" "\\\\" "||" "|>" "::" + "==" "===" "==>" "=>" "=<<" "!!" ">>" + ">>=" ">>>" ">>-" ">-" "->" "-<" "-<<" + "<*" "<*>" "<|" "<|>" "<$>" "<>" "<-" + "<<" "<<<" "<+>" ".." "..." "++" "+++" + "/=" ":::" ">=>" "->>" "<=>" "<=<" "<->"))) + (my-correct-symbol-bounds (my-ligature-list ligs #Xe100)))) + + ;; nice glyphs for haskell with hasklig + (defun my-set-hasklig-ligatures () + "Add hasklig ligatures for use with prettify-symbols-mode." + (setq prettify-symbols-alist + (append my-hasklig-ligatures prettify-symbols-alist)) + (prettify-symbols-mode)) + + (add-hook 'haskell-mode-hook 'my-set-hasklig-ligatures) +``` + +## Appendix B (Update 1): FiraCode integration + +I also created a mapping for [FiraCode][fira]. You need to grab the [additional +symbol font][symbol] that adds (most) ligatures to the unicode private use area. +Consult your system documentation on how to add it to your font cache. +Next add `"Fira Code"` and `"Fira Code Symbol"` to your font preferences. Symbol +only contains the additional characters, so you need both. + +If you are on NixOS, the font package should be on the main branch shortly, [I +added a package][symbol-pkg]. + +[fira]: https://github.com/tonsky/FiraCode/ +[symbol]: https://github.com/tonsky/FiraCode/issues/211#issuecomment-239058632 +[symbol-pkg]: https://github.com/NixOS/nixpkgs/pull/25517 + +Hereโs the mapping adjusted for FiraCode: + +```elisp + (setq my-fira-code-ligatures + (let* ((ligs '("www" "**" "***" "**/" "*>" "*/" "\\\\" "\\\\\\" + "{-" "[]" "::" ":::" ":=" "!!" "!=" "!==" "-}" + "--" "---" "-->" "->" "->>" "-<" "-<<" "-~" + "#{" "#[" "##" "###" "####" "#(" "#?" "#_" "#_(" + ".-" ".=" ".." "..<" "..." "?=" "??" ";;" "/*" + "/**" "/=" "/==" "/>" "//" "///" "&&" "||" "||=" + "|=" "|>" "^=" "$>" "++" "+++" "+>" "=:=" "==" + "===" "==>" "=>" "=>>" "<=" "=<<" "=/=" ">-" ">=" + ">=>" ">>" ">>-" ">>=" ">>>" "<*" "<*>" "<|" "<|>" + "<$" "<$>" "<!--" "<-" "<--" "<->" "<+" "<+>" "<=" + "<==" "<=>" "<=<" "<>" "<<" "<<-" "<<=" "<<<" "<~" + "<~~" "</" "</>" "~@" "~-" "~=" "~>" "~~" "~~>" "%%" + "x" ":" "+" "+" "*"))) + (my-correct-symbol-bounds (my-ligature-list ligs #Xe100)))) +``` diff --git a/users/Profpatsch/cdb.nix b/users/Profpatsch/cdb.nix new file mode 100644 index 000000000000..8cfaa3ea7ac6 --- /dev/null +++ b/users/Profpatsch/cdb.nix @@ -0,0 +1,91 @@ +{ depot, pkgs, ... }: + +let + cdbListToNetencode = depot.nix.writers.rustSimple { + name = "cdb-list-to-netencode"; + dependencies = [ + depot.third_party.rust-crates.nom + depot.users.Profpatsch.execline.exec-helpers + depot.users.Profpatsch.netencode.netencode-rs + ]; + } '' + extern crate nom; + extern crate exec_helpers; + extern crate netencode; + use std::collections::HashMap; + use std::io::BufRead; + use nom::{IResult}; + use nom::sequence::{tuple}; + use nom::bytes::complete::{tag, take}; + use nom::character::complete::{digit1, char}; + use nom::error::{context, ErrorKind, ParseError}; + use nom::combinator::{map_res}; + use netencode::{T, Tag}; + + fn usize_t(s: &[u8]) -> IResult<&[u8], usize> { + context( + "usize", + map_res( + map_res(digit1, |n| std::str::from_utf8(n)), + |s| s.parse::<usize>()) + )(s) + } + + fn parse_cdb_record(s: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + let (s, (_, klen, _, vlen, _)) = tuple(( + char('+'), + usize_t, + char(','), + usize_t, + char(':') + ))(s)?; + let (s, (key, _, val)) = tuple(( + take(klen), + tag("->"), + take(vlen), + ))(s)?; + Ok((s, (key, val))) + } + + fn main() { + let mut res = vec![]; + let stdin = std::io::stdin(); + let mut lines = stdin.lock().split(b'\n'); + loop { + match lines.next() { + None => exec_helpers::die_user_error("cdb-list-to-netencode", "stdin ended but we didnโt receive the empty line to signify the end of the cdbdump input!"), + Some(Err(err)) => exec_helpers::die_temporary("cdb-list-to-netencode", format!("could not read from stdin: {}", err)), + Some(Ok(line)) => + if &line == b"" { + // the cdbdump input ends after an empty line (double \n) + break; + } else { + match parse_cdb_record(&line) { + Ok((b"", (key, val))) => { + let (key, val) = match + std::str::from_utf8(key) + .and_then(|k| std::str::from_utf8(val).map(|v| (k, v))) { + Ok((key, val)) => (key.to_owned(), val.to_owned()), + Err(err) => exec_helpers::die_user_error("cdb-list-to-netencode", format!("cannot decode line {:?}, we only support utf8-encoded key/values pairs for now: {}", String::from_utf8_lossy(&line), err)), + }; + let _ = res.push((key, val)); + }, + Ok((rest, _)) => exec_helpers::die_user_error("cdb-list-to-netencode", format!("could not decode record line {:?}, had some trailing bytes", String::from_utf8_lossy(&line))), + Err(err) => exec_helpers::die_user_error("cdb-list-to-netencode", format!("could not decode record line {:?}: {:?}", String::from_utf8_lossy(&line), err)), + } + } + } + } + let list = T::List(res.into_iter().map( + |(k, v)| T::Record(vec![(String::from("key"), T::Text(k)), (String::from("val"), T::Text(v))].into_iter().collect()) + ).collect()); + netencode::encode(&mut std::io::stdout(), &list.to_u()); + } + + ''; + +in { + inherit + cdbListToNetencode + ; +} diff --git a/users/Profpatsch/emacs-tree-sitter-move/default.nix b/users/Profpatsch/emacs-tree-sitter-move/default.nix new file mode 100644 index 000000000000..fdc059c089b6 --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/default.nix @@ -0,0 +1,3 @@ +# nothing yet (TODO: expose shell & tool) +{...}: +{} diff --git a/users/Profpatsch/emacs-tree-sitter-move/shell.nix b/users/Profpatsch/emacs-tree-sitter-move/shell.nix new file mode 100644 index 000000000000..81d622ac73e5 --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/shell.nix @@ -0,0 +1,16 @@ +{ pkgs ? import ../../../third_party {}, ... }: +let + inherit (pkgs) lib; + + treeSitterGrammars = pkgs.runCommandLocal "grammars" {} '' + mkdir -p $out/bin + ${lib.concatStringsSep "\n" + (lib.mapAttrsToList (name: src: "ln -s ${src}/parser $out/bin/${name}.so") pkgs.tree-sitter.builtGrammars)}; + ''; + +in pkgs.mkShell { + buildInputs = [ + pkgs.tree-sitter.builtGrammars.python + ]; + TREE_SITTER_GRAMMAR_DIR = treeSitterGrammars; +} diff --git a/users/Profpatsch/emacs-tree-sitter-move/test.json b/users/Profpatsch/emacs-tree-sitter-move/test.json new file mode 100644 index 000000000000..d9f8075976d6 --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/test.json @@ -0,0 +1,14 @@ +{ + "foo": { + "x": [ 1, 2, 3, 4 ], + "bar": "test" + }, + "foo": { + "x": [ 1, 2, 3, 4 ], + "bar": "test" + }, + "foo": { + "x": [ 1, 2, 3, 4 ], + "bar": "test" + } +} diff --git a/users/Profpatsch/emacs-tree-sitter-move/test.py b/users/Profpatsch/emacs-tree-sitter-move/test.py new file mode 100644 index 000000000000..0f57bae035da --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/test.py @@ -0,0 +1,13 @@ +(4 + 5 + 5) + +def foo(a, b, c) + +def bar(a, b): + 4 + 4 + 4 + +[1, 4, 5, 10] + +def foo(): + pass diff --git a/users/Profpatsch/emacs-tree-sitter-move/test.sh b/users/Profpatsch/emacs-tree-sitter-move/test.sh new file mode 100644 index 000000000000..681081f5909d --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/test.sh @@ -0,0 +1,14 @@ +function foo () { + local x=123 +} + +function bar () { + local x=123 +} + +echo abc def \ + gef gef + +printf \ + "%s\n" \ + haha diff --git a/users/Profpatsch/emacs-tree-sitter-move/tmp.el b/users/Profpatsch/emacs-tree-sitter-move/tmp.el new file mode 100644 index 000000000000..88d13fa45b81 --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/tmp.el @@ -0,0 +1,28 @@ +(defun tree-sitter-load-from-grammar-dir (grammar-dir sym lang-name) + (tree-sitter-load + sym + (format "%s/bin/%s" + (getenv grammar-dir) + lang-name))) + +(defun tree-sitter-init-tmp-langs (alist) + (mapcar + (lambda (lang) + (pcase-let ((`(,name ,sym ,mode) lang)) + (tree-sitter-load-from-grammar-dir "TREE_SITTER_GRAMMAR_DIR" sym name) + (cons mode sym))) + alist)) + + +(setq tree-sitter-major-mode-language-alist + (tree-sitter-init-tmp-langs + '(("python" python python-mode) + ("json" json js-mode) + ("bash" bash sh-mode) + ))) + +(define-key evil-normal-state-map (kbd "C-.") #'tree-sitter-move-reset) +(define-key evil-normal-state-map (kbd "C-<right>") #'tree-sitter-move-right) +(define-key evil-normal-state-map (kbd "C-<left>") #'tree-sitter-move-left) +(define-key evil-normal-state-map (kbd "C-<up>") #'tree-sitter-move-up) +(define-key evil-normal-state-map (kbd "C-<down>") #'tree-sitter-move-down) diff --git a/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el b/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el new file mode 100644 index 000000000000..907e1e4081bc --- /dev/null +++ b/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el @@ -0,0 +1,139 @@ +;; this is not an actual cursor, just a node. +;; Itโs not super efficient, but cursors canโt be *set* to an arbitrary +;; subnode, because they canโt access the parent otherwise. +;; Weโd need a way to reset the cursor and walk down to the node?! +(defvar-local tree-sitter-move--cursor nil + "the buffer-local cursor used for movement") + +(defvar-local tree-sitter-move--debug-overlay nil + "an overlay used to visually display the region currently marked by the cursor") + +;;;;; TODO: should everything use named nodes? Only some things? +;;;;; maybe there should be a pair of functions for everything? +;;;;; For now restrict to named nodes. + +(defun tree-sitter-move--setup () + ;; TODO + (progn + ;; TODO: if tree-sitter-mode fails to load, display a better error + (tree-sitter-mode t) + (setq tree-sitter-move--cursor (tsc-root-node tree-sitter-tree)) + (add-variable-watcher + 'tree-sitter-move--cursor + #'tree-sitter-move--debug-overlay-update))) + +(defun tree-sitter-move--debug-overlay-update (sym newval &rest _args) + "variable-watcher to update the debug overlay when the cursor changes" + (let ((start (tsc-node-start-position newval)) + (end (tsc-node-end-position newval))) + (symbol-macrolet ((o tree-sitter-move--debug-overlay)) + (if o + (move-overlay o start end) + (setq o (make-overlay start end)) + (overlay-put o 'face 'highlight) + )))) + +(defun tree-sitter-move--debug-overlay-teardown () + "Turn of the overlay visibility and delete the overlay object" + (when tree-sitter-move--debug-overlay + (delete-overlay tree-sitter-move--debug-overlay) + (setq tree-sitter-move--debug-overlay nil))) + +(defun tree-sitter-move--teardown () + (setq tree-sitter-move--cursor nil) + (tree-sitter-move--debug-overlay-teardown) + (tree-sitter-mode nil)) + +;; Get the syntax node the cursor is on. +(defun tsc-get-named-node-at-point () + (let ((p (point))) + (tsc-get-named-descendant-for-position-range + (tsc-root-node tree-sitter-tree) p p))) + +;; TODO: is this function necessary? +;; Maybe tree-sitter always guarantees that parents are named? +(defun tsc-get-named-parent (node) + (when-let ((parent (tsc-get-parent node))) + (while (and parent (not (tsc-node-named-p parent))) + (setq parent (tsc-get-parent parent))) + parent)) + +(defun tsc-get-first-named-node-with-siblings-up (node) + "Returns the first 'upwards' node that has siblings. That includes the current + node, so if the given node has siblings, it is returned. Returns nil if there + is no such node until the root" + (when-let ((has-siblings-p + (lambda (parent-node) + (> (tsc-count-named-children parent-node) + 1))) + (cur node) + (parent (tsc-get-named-parent node))) + (while (and parent (not (funcall has-siblings-p parent))) + (setq cur parent) + (setq parent (tsc-get-named-parent cur))) + cur)) + +(defun tree-sitter-move--set-cursor-to-node (node) + (setq tree-sitter-move--cursor node)) + +(defun tree-sitter-move--set-cursor-to-node-at-point () + (tree-sitter-move--set-cursor-to-node (tsc-get-named-node-at-point))) + +(defun tree-sitter-move--move-point-to-node (node) + (set-window-point + (selected-window) + (tsc-node-start-position node))) + + +;; interactive commands (โdo what I expectโ section) + +(defun tree-sitter-move-reset () + (interactive) + (tree-sitter-move--set-cursor-to-node-at-point)) + +(defun tree-sitter-move-right () + (interactive) + (tree-sitter-move--move-skip-non-sibling-nodes 'tsc-get-next-named-sibling)) + +(defun tree-sitter-move-left () + (interactive) + (tree-sitter-move--move-skip-non-sibling-nodes 'tsc-get-prev-named-sibling)) + +(defun tree-sitter-move-up () + (interactive) + (tree-sitter-move--move-skip-non-sibling-nodes 'tsc-get-parent)) + +;; TODO: does not skip siblings yet, because the skip function only goes up (not down) +(defun tree-sitter-move-down () + (interactive) + (tree-sitter-move--move-if-possible (lambda (n) (tsc-get-nth-named-child n 0)))) + +(defun tree-sitter-move--move-skip-non-sibling-nodes (move-fn) + "Moves to the sidewards next sibling. If the current node does not have siblings, go + upwards until something has siblings and then move to the side (right or left)." + (tree-sitter-move--move-if-possible + (lambda (cur) + (when-let ((with-siblings + (tsc-get-first-named-node-with-siblings-up cur))) + (funcall move-fn with-siblings))))) + +(defun tree-sitter-move--move-if-possible (dir-fn) + (let ((next (funcall dir-fn tree-sitter-move--cursor))) + (when next + (tree-sitter-move--set-cursor-to-node next) + (tree-sitter-move--move-point-to-node next)))) + +; mostly stolen from tree-sitter-mode +;;;###autoload +(define-minor-mode tree-sitter-move-mode + "Minor mode to do cursor movements via tree-sitter" + :init-value nil + :lighter " tree-sitter-move" + (if tree-sitter-move-mode + (tree-sitter--error-protect + (progn + (tree-sitter-move--setup)) + (setq tree-sitter-move-mode nil) + (tree-sitter-move--teardown)) + (lambda ()) + (tree-sitter-move--teardown))) diff --git a/users/Profpatsch/execline/default.nix b/users/Profpatsch/execline/default.nix new file mode 100644 index 000000000000..1f75b97591fc --- /dev/null +++ b/users/Profpatsch/execline/default.nix @@ -0,0 +1,12 @@ +{ depot, pkgs, lib, ... }: + +let + exec-helpers = depot.nix.writers.rustSimpleLib { + name = "exec-helpers"; + } (builtins.readFile ./exec_helpers.rs); + +in depot.nix.readTree.drvTargets { + inherit + exec-helpers + ; +} diff --git a/users/Profpatsch/execline/exec_helpers.rs b/users/Profpatsch/execline/exec_helpers.rs new file mode 100644 index 000000000000..b9e1f5797386 --- /dev/null +++ b/users/Profpatsch/execline/exec_helpers.rs @@ -0,0 +1,113 @@ +use std::os::unix::process::CommandExt; +use std::ffi::OsStr; +use std::os::unix::ffi::{OsStringExt, OsStrExt}; + +pub fn no_args(current_prog_name: &str) -> () { + let mut args = std::env::args_os(); + // remove argv[0] + let _ = args.nth(0); + if args.len() > 0 { + die_user_error(current_prog_name, format!("Expected no arguments, got {:?}", args.collect::<Vec<_>>())) + } +} + +pub fn args(current_prog_name: &str, no_of_positional_args: usize) -> Vec<Vec<u8>> { + let mut args = std::env::args_os(); + // remove argv[0] + let _ = args.nth(0); + if args.len() != no_of_positional_args { + die_user_error(current_prog_name, format!("Expected {} arguments, got {}, namely {:?}", no_of_positional_args, args.len(), args.collect::<Vec<_>>())) + } + args.map(|arg| arg.into_vec()).collect() +} + +pub fn args_for_exec(current_prog_name: &str, no_of_positional_args: usize) -> (Vec<Vec<u8>>, Vec<Vec<u8>>) { + let mut args = std::env::args_os(); + // remove argv[0] + let _ = args.nth(0); + let mut args = args.map(|arg| arg.into_vec()); + let mut pos_args = vec![]; + // get positional args + for i in 1..no_of_positional_args+1 { + pos_args.push( + args.nth(0).expect( + &format!("{}: expects {} positional args, only got {}", current_prog_name, no_of_positional_args, i)) + ); + } + // prog... is the rest of the iterator + let prog : Vec<Vec<u8>> = args.collect(); + (pos_args, prog) +} + +pub fn exec_into_args<'a, 'b, Args, Arg, Env, Key, Val>(current_prog_name: &str, args: Args, env_additions: Env) -> ! + where + Args: IntoIterator<Item = Arg>, + Arg: AsRef<[u8]>, + Env: IntoIterator<Item = (Key, Val)>, + Key: AsRef<[u8]>, + Val: AsRef<[u8]>, +{ + // TODO: is this possible without collecting into a Vec first, just leaving it an IntoIterator? + let args = args.into_iter().collect::<Vec<Arg>>(); + let mut args = args.iter().map(|v| OsStr::from_bytes(v.as_ref())); + let prog = args.nth(0).expect(&format!("{}: first argument must be an executable", current_prog_name)); + // TODO: same here + let env = env_additions.into_iter().collect::<Vec<(Key, Val)>>(); + let env = env.iter().map(|(k,v)| (OsStr::from_bytes(k.as_ref()), OsStr::from_bytes(v.as_ref()))); + let err = std::process::Command::new(prog).args(args).envs(env).exec(); + die_missing_executable(current_prog_name, format!("exec failed: {}, while trying to execing into {:?}", err, prog)); +} + +/// Exit 1 to signify a generic expected error +/// (e.g. something that sometimes just goes wrong, like a nix build). +pub fn die_expected_error<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(1, current_prog_name, msg) +} + +/// Exit 100 to signify a user error (โthe user is holding it wrongโ). +/// This is a permanent error, if the program is executed the same way +/// it should crash with 100 again. +pub fn die_user_error<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(100, current_prog_name, msg) +} + +/// Exit 101 to signify an unexpected crash (failing assertion or panic). +/// This is the same exit code that `panic!()` emits. +pub fn die_panic<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(101, current_prog_name, msg) +} + +/// Exit 111 to signify a temporary error (such as resource exhaustion) +pub fn die_temporary<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(111, current_prog_name, msg) +} + +/// Exit 126 to signify an environment problem +/// (the user has set up stuff incorrectly so the program cannot work) +pub fn die_environment_problem<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(126, current_prog_name, msg) +} + +/// Exit 127 to signify a missing executable. +pub fn die_missing_executable<S>(current_prog_name: &str, msg: S) -> ! +where S: AsRef<str> +{ + die_with(127, current_prog_name, msg) +} + +fn die_with<S>(status: i32, current_prog_name: &str, msg: S) -> ! + where S: AsRef<str> +{ + eprintln!("{}: {}", current_prog_name, msg.as_ref()); + std::process::exit(status) +} diff --git a/users/Profpatsch/imap-idle.nix b/users/Profpatsch/imap-idle.nix new file mode 100644 index 000000000000..3ad5375d89af --- /dev/null +++ b/users/Profpatsch/imap-idle.nix @@ -0,0 +1,14 @@ +{ depot, pkgs, lib, ... }: + +let + imap-idle = depot.nix.writers.rustSimple { + name = "imap-idle"; + dependencies = [ + depot.users.Profpatsch.arglib.netencode.rust + depot.third_party.rust-crates.imap + depot.third_party.rust-crates.epoll + depot.users.Profpatsch.execline.exec-helpers + ]; + } (builtins.readFile ./imap-idle.rs); + +in imap-idle diff --git a/users/Profpatsch/imap-idle.rs b/users/Profpatsch/imap-idle.rs new file mode 100644 index 000000000000..9dce736d0d8a --- /dev/null +++ b/users/Profpatsch/imap-idle.rs @@ -0,0 +1,132 @@ +extern crate exec_helpers; +// extern crate arglib_netencode; +// extern crate netencode; +extern crate imap; +extern crate epoll; + +// use netencode::dec; +use std::convert::TryFrom; +use std::io::{Read, Write}; +use std::fs::File; +use std::os::unix::io::{FromRawFd, AsRawFd, RawFd}; +use std::time::Duration; +use imap::extensions::idle::SetReadTimeout; + +/// Implements an UCSPI client that wraps fd 6 & 7 +/// and implements Write and Read with a timeout. +/// See https://cr.yp.to/proto/ucspi.txt +#[derive(Debug)] +struct UcspiClient { + read: File, + read_epoll_fd: RawFd, + read_timeout: Option<Duration>, + write: File, +} + +impl UcspiClient { + /// Use fd 6 and 7 to connect to the net, as is specified. + /// Unsafe because fd 6 and 7 are global resources and we donโt mutex them. + pub unsafe fn new_from_6_and_7() -> std::io::Result<Self> { + unsafe { + let read_epoll_fd = epoll::create(false)?; + Ok(UcspiClient { + read: File::from_raw_fd(6), + read_epoll_fd, + read_timeout: None, + write: File::from_raw_fd(7) + }) + } + } +} + +/// Emulates set_read_timeout() like on a TCP socket with an epoll on read. +/// The BSD socket API is rather bad, so fd != fd, +/// and if we cast the `UcspiClient` fds to `TcpStream` instead of `File`, +/// weโd break any UCSPI client programs that *donโt* connect to TCP. +/// Instead we use the (linux) `epoll` API in read to wait on the timeout. +impl SetReadTimeout for UcspiClient { + fn set_read_timeout(&mut self, timeout: Option<Duration>) -> imap::Result<()> { + self.read_timeout = timeout; + Ok(()) + } +} + +impl Read for UcspiClient { + // TODO: test the epoll code with a short timeout + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + const NO_DATA : u64 = 0; + // in order to implement the read_timeout, + // we use epoll to wait for either data or time out + epoll::ctl( + self.read_epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + self.read.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA) + )?; + let UNUSED = epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA); + let wait = epoll::wait( + self.read_epoll_fd, + match self.read_timeout { + Some(duration) => i32::try_from(duration.as_millis()).expect("duration too big for epoll"), + None => -1 // infinite + }, + // event that was generated; but we donโt care + &mut vec![UNUSED; 1][..], + ); + // Delete the listen fd from the epoll fd before reacting + // (otherwise it fails on the next read with `EPOLL_CTL_ADD`) + epoll::ctl( + self.read_epoll_fd, + epoll::ControlOptions::EPOLL_CTL_DEL, + self.read.as_raw_fd(), + UNUSED + )?; + match wait { + // timeout happened (0 events) + Ok(0) => Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "ucspi read timeout")), + // its ready for reading, we can read + Ok(_) => self.read.read(buf), + // error + err => err, + } + } +} + +/// Just proxy through the `Write` of the write fd. +impl Write for UcspiClient { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.write.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + self.write.flush() + } +} + +/// Connect to IMAP account and listen for new mails on the INBOX. +fn main() { + exec_helpers::no_args("imap-idle"); + + // TODO: use arglib_netencode + let username = std::env::var("IMAP_USERNAME").expect("username"); + let password = std::env::var("IMAP_PASSWORD").expect("password"); + + let net = unsafe { + UcspiClient::new_from_6_and_7().expect("no ucspi client for you") + }; + let client = imap::Client::new(net); + let mut session = client.login(username, password).map_err(|(err, _)| err).expect("unable to login"); + eprintln!("{:#?}", session); + let list = session.list(None, Some("*")); + eprintln!("{:#?}", list); + let mailbox = session.examine("INBOX"); + eprintln!("{:#?}", mailbox); + fn now() -> String { + String::from_utf8_lossy(&std::process::Command::new("date").output().unwrap().stdout).trim_right().to_string() + } + loop { + eprintln!("{}: idling on INBOX", now()); + let mut handle = session.idle().expect("cannot idle on INBOX"); + let () = handle.wait_keepalive().expect("waiting on idle failed"); + eprintln!("{}: The mailbox has changed!", now()); + } +} diff --git a/users/Profpatsch/lens.nix b/users/Profpatsch/lens.nix new file mode 100644 index 000000000000..58d9c27f5242 --- /dev/null +++ b/users/Profpatsch/lens.nix @@ -0,0 +1,136 @@ +{ ... }: +let + id = x: x; + + const = x: y: x; + + comp = f: g: x: f (g x); + + _ = v: f: f v; + + # Profunctor (p :: Type -> Type -> Type) + Profunctor = rec { + # dimap :: (a -> b) -> (c -> d) -> p b c -> p a d + dimap = f: g: x: lmap f (rmap g x); + # lmap :: (a -> b) -> p b c -> p a c + lmap = f: dimap f id; + # rmap :: (c -> d) -> p b c -> p b d + rmap = g: dimap id g; + }; + + # Profunctor (->) + profunctorFun = Profunctor // { + # dimap :: (a -> b) -> (c -> d) -> (b -> c) -> a -> d + dimap = ab: cd: bc: a: cd (bc (ab a)); + # lmap :: (a -> b) -> (b -> c) -> (a -> c) + lmap = ab: bc: a: bc (ab a); + # rmap :: (c -> d) -> (b -> c) -> (b -> d) + rmap = cd: bc: b: cd (bc b); + }; + + tuple = fst: snd: { + inherit fst snd; + }; + + swap = {fst, snd}: { + fst = snd; + snd = fst; + }; + + # Profunctor p => Strong (p :: Type -> Type -> Type) + Strong = pro: pro // rec { + # firstP :: p a b -> p (a, c) (b, c) + firstP = pab: pro.dimap swap swap (pro.secondP pab); + # secondP :: p a b -> p (c, a) (c, b) + secondP = pab: pro.dimap swap swap (pro.firstP pab); + }; + + # Strong (->) + strongFun = Strong profunctorFun // { + # firstP :: (a -> b) -> (a, c) -> (b, c) + firstP = f: { fst, snd }: { fst = f fst; inherit snd; }; + # secondP :: (a -> b) -> (c, a) -> (c, b) + secondP = f: { snd, fst }: { snd = f snd; inherit fst; }; + }; + + # Iso s t a b :: forall p. Profunctor p -> p a b -> p s t + + # iso :: (s -> a) -> (b -> t) -> Iso s t a b + iso = pro: pro.dimap; + + # Lens s t a b :: forall p. Strong p -> p a b -> p s t + + # lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b + lens = strong: get: set: pab: + lensP + strong + (s: tuple (get s) (b: set s b)) + pab; + + # lensP :: (s -> (a, b -> t)) -> Lens s t a b + lensP = strong: to: pab: + strong.dimap + to + ({fst,snd}: snd fst) + (strong.firstP pab); + + # first element of a tuple + # _1 :: Lens (a, c) (b, c) a b + _1 = strong: strong.firstP; + + # second element of a tuple + # _2 :: Lens (c, a) (c, b) a b + _2 = strong: strong.secondP; + + # a the given field in the record + # field :: (f :: String) -> Lens { f :: a; ... } { f :: b; ... } a b + field = name: strong: + lens + strong + (attrs: attrs.${name}) + (attrs: a: attrs // { ${name} = a; }); + + # Setter :: (->) a b -> (->) s t + # Setter :: (a -> b) -> (s -> t) + + + # Subclasses of profunctor for (->). + # We only have Strong for now, but when we implement Choice we need to add it here. + profunctorSubclassesFun = strongFun; + + # over :: Setter s t a b -> (a -> b) -> s -> t + over = setter: + # A setter needs to be instanced to the profunctor-subclass instances of (->). + (setter profunctorSubclassesFun); + + # set :: Setter s t a b -> b -> s -> t + set = setter: b: over setter (const b); + + # combine a bunch of optics, for the subclass instance of profunctor you give it. + optic = accessors: profunctorSubclass: + builtins.foldl' comp id + (map (accessor: accessor profunctorSubclass) accessors); + + +in { + inherit + id + _ + const + comp + Profunctor + profunctorFun + Strong + strongFun + iso + lens + optic + _1 + _2 + field + tuple + swap + over + set + ; +} diff --git a/users/Profpatsch/lib.nix b/users/Profpatsch/lib.nix new file mode 100644 index 000000000000..e3d59b7d8beb --- /dev/null +++ b/users/Profpatsch/lib.nix @@ -0,0 +1,81 @@ +{ depot, pkgs, ... }: +let + bins = depot.nix.getBins pkgs.coreutils [ "printf" "echo" "cat" "printenv" "tee" ] + // depot.nix.getBins pkgs.bash [ "bash" ] + // depot.nix.getBins pkgs.fdtools [ "multitee" ] + ; + + # Print `msg` and and argv to stderr, then execute into argv + debugExec = msg: depot.nix.writeExecline "debug-exec" {} [ + "if" [ + "fdmove" "-c" "1" "2" + "if" [ bins.printf "%s: " msg ] + "if" [ bins.echo "$@" ] + ] + "$@" + ]; + + # Print stdin to stderr and stdout + eprint-stdin = depot.nix.writeExecline "eprint-stdin" {} [ + "pipeline" [ bins.multitee "0-1,2" ] "$@" + ]; + + # Assume the input on stdin is netencode, pretty print it to stderr and forward it to stdout + eprint-stdin-netencode = depot.nix.writeExecline "eprint-stdin-netencode" {} [ + "pipeline" [ + # move stdout to 3 + "fdmove" "3" "1" + # the multitee copies stdin to 1 (the other pipeline end) and 3 (the stdout of the outer pipeline block) + "pipeline" [ bins.multitee "0-1,3" ] + # make stderr the stdout of pretty, merging with the stderr of pretty + "fdmove" "-c" "1" "2" + depot.users.Profpatsch.netencode.pretty + ] + "$@" + ]; + + # print the given environment variable in $1 to stderr, then execute into the rest of argv + eprintenv = depot.nix.writeExecline "eprintenv" { readNArgs = 1; } [ + "ifelse" [ "fdmove" "-c" "1" "2" bins.printenv "$1" ] + [ "$@" ] + "if" [ depot.tools.eprintf "eprintenv: could not find \"\${1}\" in the environment\n" ] + "$@" + ]; + + # Split stdin into two commands, given by a block and the rest of argv + # + # Example (execline): + # + # pipeline [ echo foo ] + # split-stdin [ fdmove 1 2 foreground [ cat ] echo "bar" ] cat + # + # stdout: foo\n + # stderr: foo\nbar\n + split-stdin = depot.nix.writeExecline "split-stdin" { argMode = "env"; } [ + "pipeline" [ + # this is horrible yes but the quickest way I knew how to implement it + "runblock" "1" bins.bash "-c" ''${bins.tee} >("$@")'' "bash-split-stdin" + ] + "runblock" "-r" "1" + ]; + + # remove everything but a few selected environment variables + runInEmptyEnv = keepVars: + let + importas = pkgs.lib.concatMap (var: [ "importas" "-i" var var ]) keepVars; + # we have to explicitely call export here, because PATH is probably empty + export = pkgs.lib.concatMap (var: [ "${pkgs.execline}/bin/export" var ''''${${var}}'' ]) keepVars; + in depot.nix.writeExecline "empty-env" {} + (importas ++ [ "emptyenv" ] ++ export ++ [ "${pkgs.execline}/bin/exec" "$@" ]); + + +in { + inherit + debugExec + eprint-stdin + eprint-stdin-netencode + eprintenv + split-stdin + runInEmptyEnv + ; +} diff --git a/users/Profpatsch/netencode/README.md b/users/Profpatsch/netencode/README.md new file mode 100644 index 000000000000..67cb843a58c7 --- /dev/null +++ b/users/Profpatsch/netencode/README.md @@ -0,0 +1,111 @@ +# netencode 0.1-unreleased + +[bencode][] and [netstring][]-inspired pipe format that should be trivial to generate correctly in every context (only requires a `byte_length()` and a `printf()`), easy to parse (100 lines of code or less), mostly human-decipherable for easy debugging, and support nested record and sum types. + + +## scalars + +Scalars have the format `[type prefix][size]:[value],`. + +where size is a natural number without leading zeroes. + +### unit + +The unit (`u`) has only one value. + +* The unit is: `u,` + +### numbers + +Naturals (`n`) and Integers (`i`), with a maximum size in bits. + +Bit sizes are specified in 2^n increments, 1 to 9 (`n1`..`n9`, `i1`..`n9`). + +* Natural `1234` that fits in 32 bits (2^5): `n5:1234,` +* Integer `-42` that fits in 8 bits (2^3): `i3:-42,` +* Integer `23` that fits in 64 bits (2^6): `i6:23,` +* Integer `-1` that fits in 512 bits (2^9): `i9:-1,` +* Natural `0` that fits in 1 bit (2^1): `n1:0,` + +An implementation can define the biggest numbers it supports, and has to throw an error for anything bigger. It has to support everything smaller, so for example if you support up to i6/n6, you have to support 1โ6 as well. An implementation could support up to the current architectureโs wordsize for example. + +Floats are not supported, you can implement fixed-size decimals or ratios using integers. + +### booleans + +A boolean is represented as `n1`. + +* `n1:0,`: false +* `n1:1,`: true + +TODO: should we add `f,` and `t,`? + +### text + +Text (`t`) that *must* be encoded as UTF-8, starting with its length in bytes: + +* The string `hello world` (11 bytes): `t11:hello world,` +* The string `ไปๆฅใฏ` (9 bytes): `t9:ไปๆฅใฏ,` +* The string `:,` (2 bytes): `t2::,,` +* The empty sting `` (0 bytes): `t0:,` + +### binary + +Arbitrary binary strings (`b`) that can contain any data, starting with its length in bytes. + +* The ASCII string `hello world` as binary data (11 bytes): `b11:hello world,` +* The empty binary string (0 bytes): `b0:,` +* The bytestring with `^D` (1 byte): `b1:,` + +Since the binary strings are length-prefixd, they can contain `\0` and no escaping is required. Care has to be taken in languages with `\0`-terminated bytestrings. + +Use text (`t`) if you have utf-8 encoded data. + +## tagged values + +### tags + +A tag (`<`) gives a value a name. The tag is UTF-8 encoded, starting with its length in bytes and proceeding with the value. + +* The tag `foo` (3 bytes) tagging the text `hello` (5 bytes): `<3:foo|t5:hello,` +* The tag `` (0 bytes) tagging the 8-bit integer 0: `<0:|i3:0,` + +### records (products/records), also maps + +A record (`{`) is a concatenation of tags (`<`). It needs to be closed with `}`. +If tag names repeat the later ones should be ignored. Ordering does not matter. + +Similar to text, records start with the length of their *whole encoded content*, in bytes. This makes it possible to treat their contents as opaque bytestrings. + +* There is no empty record. (TODO: make the empty record the unit type, remove `u,`?) +* A record with one empty field, `foo`: `{9:<3:foo|u,}` +* A record with two fields, `foo` and `x`: `{21:<3:foo|u,<1:x|t3:baz,}` +* The same record: `{21:<1:x|t3:baz,<3:foo|u,}` +* The same record (later occurences of fields are ignored): `{28:<1:x|t3:baz,<3:foo|u,<1:x|u,}` + +### sums (tagged unions) + +Simply a tagged value. The tag marker `<` indicates it is a sum if it appears outside of a record. + +## lists + +A list (`[`) imposes an ordering on a sequence of values. It needs to be closed with `]`. Values in it are simply concatenated. + +Similar to records, lists start with the length of their whole encoded content. + +* The empty list: `[0:]` +* The list with one element, the string `foo`: `[7:t3:foo,]` +* The list with text `foo` followed by i3 `-42`: `[14:t3:foo,i3:-42,]` +* The list with `Some` and `None` tags: `[33:<4:Some|t3:foo,<4None|u,<4None|u,]` + +## motivation + +TODO + +## guarantees + +TODO: do I want unique representation (bijection like bencode?) This would put more restrictions on the generator, like sorting records in lexicographic order, but would make it possible to compare without decoding + + +[bencode]: https://en.wikipedia.org/wiki/Bencode +[netstring]: https://en.wikipedia.org/wiki/Netstring diff --git a/users/Profpatsch/netencode/default.nix b/users/Profpatsch/netencode/default.nix new file mode 100644 index 000000000000..739bda3d78c9 --- /dev/null +++ b/users/Profpatsch/netencode/default.nix @@ -0,0 +1,149 @@ +{ depot, pkgs, lib, ... }: + +let + netencode-rs = depot.nix.writers.rustSimpleLib { + name = "netencode"; + dependencies = [ + depot.third_party.rust-crates.nom + depot.users.Profpatsch.execline.exec-helpers + ]; + } (builtins.readFile ./netencode.rs); + + gen = import ./gen.nix { inherit lib; }; + + pretty-rs = depot.nix.writers.rustSimpleLib { + name = "netencode-pretty"; + dependencies = [ + netencode-rs + ]; + } (builtins.readFile ./pretty.rs); + + pretty = depot.nix.writers.rustSimple { + name = "netencode-pretty"; + dependencies = [ + netencode-rs + pretty-rs + depot.users.Profpatsch.execline.exec-helpers + ]; + } '' + extern crate netencode; + extern crate netencode_pretty; + extern crate exec_helpers; + + fn main() { + let (_, prog) = exec_helpers::args_for_exec("netencode-pretty", 0); + let mut buf = vec![]; + let u = netencode::u_from_stdin_or_die_user_error("netencode-pretty", &mut buf); + match netencode_pretty::Pretty::from_u(u).print_multiline(&mut std::io::stdout()) { + Ok(()) => {}, + Err(err) => exec_helpers::die_temporary("netencode-pretty", format!("could not write to stdout: {}", err)) + } + } + ''; + + netencode-mustache = depot.nix.writers.rustSimple { + name = "netencode_mustache"; + dependencies = [ + depot.users.Profpatsch.arglib.netencode.rust + netencode-rs + depot.third_party.rust-crates.mustache + ]; + } (builtins.readFile ./netencode-mustache.rs); + + + record-get = depot.nix.writers.rustSimple { + name = "record-get"; + dependencies = [ + netencode-rs + depot.users.Profpatsch.execline.exec-helpers + depot.users.Profpatsch.arglib.netencode.rust + ]; + } '' + extern crate netencode; + extern crate arglib_netencode; + extern crate exec_helpers; + use netencode::{encode, dec}; + use netencode::dec::{Decoder, DecodeError}; + + fn main() { + let mut buf = vec![]; + let args = exec_helpers::args("record-get", 1); + let field = match std::str::from_utf8(&args[0]) { + Ok(f) => f, + Err(_e) => exec_helpers::die_user_error("record-get", format!("The field name needs to be valid unicode")) + }; + let u = netencode::u_from_stdin_or_die_user_error("record-get", &mut buf); + match (dec::RecordDot {field, inner: dec::AnyU }).dec(u) { + Ok(u) => encode(&mut std::io::stdout(), &u).expect("encoding to stdout failed"), + Err(DecodeError(err)) => exec_helpers::die_user_error("record-get", err) + } + } + ''; + + record-splice-env = depot.nix.writers.rustSimple { + name = "record-splice-env"; + dependencies = [ + netencode-rs + depot.users.Profpatsch.execline.exec-helpers + ]; + } '' + extern crate netencode; + extern crate exec_helpers; + use netencode::dec::{Record, Try, ScalarAsBytes, Decoder, DecodeError}; + + fn main() { + let mut buf = vec![]; + let u = netencode::u_from_stdin_or_die_user_error("record-splice-env", &mut buf); + let (_, prog) = exec_helpers::args_for_exec("record-splice-env", 0); + match Record(Try(ScalarAsBytes)).dec(u) { + Ok(map) => { + exec_helpers::exec_into_args( + "record-splice-env", + prog, + // some elements canโt be decoded as scalars, so just ignore them + map.into_iter().filter_map(|(k, v)| v.map(|v2| (k, v2))) + ); + }, + Err(DecodeError(err)) => exec_helpers::die_user_error("record-splice-env", err), + } + } + ''; + + env-splice-record = depot.nix.writers.rustSimple { + name = "env-splice-record"; + dependencies = [ + netencode-rs + depot.users.Profpatsch.execline.exec-helpers + ]; + } '' + extern crate netencode; + extern crate exec_helpers; + use netencode::{T}; + use std::os::unix::ffi::OsStringExt; + + fn main() { + exec_helpers::no_args("env-splice-record"); + let mut res = std::collections::HashMap::new(); + for (key, val) in std::env::vars_os() { + match (String::from_utf8(key.into_vec()), String::from_utf8(val.into_vec())) { + (Ok(k), Ok(v)) => { let _ = res.insert(k, T::Text(v)); }, + // same as in record-splice-env, we ignore non-utf8 variables + (_, _) => {}, + } + } + netencode::encode(&mut std::io::stdout(), &T::Record(res).to_u()).unwrap() + } + ''; + +in depot.nix.readTree.drvTargets { + inherit + netencode-rs + pretty-rs + pretty + netencode-mustache + record-get + record-splice-env + env-splice-record + gen + ; +} diff --git a/users/Profpatsch/netencode/gen.nix b/users/Profpatsch/netencode/gen.nix new file mode 100644 index 000000000000..305ff7b08dd6 --- /dev/null +++ b/users/Profpatsch/netencode/gen.nix @@ -0,0 +1,69 @@ +{ lib }: +let + + netstring = tag: suffix: s: + "${tag}${toString (builtins.stringLength s)}:${s}${suffix}"; + + unit = "u,"; + + n1 = b: if b then "n1:1," else "n1:0,"; + + n = i: n: "n${toString i}:${toString n},"; + i = i: n: "i${toString i}:${toString n},"; + + n3 = n 3; + n6 = n 6; + n7 = n 7; + + i3 = i 3; + i6 = i 6; + i7 = i 7; + + text = netstring "t" ","; + binary = netstring "b" ","; + + tag = key: val: netstring "<" "|" key + val; + + concatStrings = builtins.concatStringsSep ""; + + record = lokv: netstring "{" "}" + (concatStrings (map ({key, val}: tag key val) lokv)); + + list = l: netstring "[" "]" (concatStrings l); + + dwim = val: + let match = { + "bool" = n1; + "int" = i6; + "string" = text; + "set" = attrs: + # it could be a derivation, then just return the path + if attrs.type or "" == "derivation" then text "${attrs}" + else + record (lib.mapAttrsToList + (k: v: { + key = k; + val = dwim v; + }) attrs); + "list" = l: list (map dwim l); + }; + in match.${builtins.typeOf val} val; + +in { + inherit + unit + n1 + n3 + n6 + n7 + i3 + i6 + i7 + text + binary + tag + record + list + dwim + ; +} diff --git a/users/Profpatsch/netencode/netencode-mustache.rs b/users/Profpatsch/netencode/netencode-mustache.rs new file mode 100644 index 000000000000..ee7bafed2250 --- /dev/null +++ b/users/Profpatsch/netencode/netencode-mustache.rs @@ -0,0 +1,53 @@ +extern crate netencode; +extern crate mustache; +extern crate arglib_netencode; + +use mustache::{Data}; +use netencode::{T}; +use std::collections::HashMap; +use std::os::unix::ffi::{OsStrExt}; +use std::io::{Read}; + +fn netencode_to_mustache_data_dwim(t: T) -> Data { + match t { + // TODO: good idea? + T::Unit => Data::Null, + T::N1(b) => Data::Bool(b), + T::N3(u) => Data::String(u.to_string()), + T::N6(u) => Data::String(u.to_string()), + T::N7(u) => Data::String(u.to_string()), + T::I3(i) => Data::String(i.to_string()), + T::I6(i) => Data::String(i.to_string()), + T::I7(i) => Data::String(i.to_string()), + T::Text(s) => Data::String(s), + T::Binary(b) => unimplemented!(), + T::Sum(tag) => unimplemented!(), + T::Record(xs) => Data::Map( + xs.into_iter() + .map(|(key, val)| (key, netencode_to_mustache_data_dwim(val))) + .collect::<HashMap<_,_>>() + ), + T::List(xs) => Data::Vec( + xs.into_iter() + .map(|x| netencode_to_mustache_data_dwim(x)) + .collect::<Vec<_>>() + ), + } +} + +pub fn from_stdin() -> () { + let data = netencode_to_mustache_data_dwim( + arglib_netencode::arglib_netencode("netencode-mustache", Some(std::ffi::OsStr::new("TEMPLATE_DATA"))) + ); + let mut stdin = String::new(); + std::io::stdin().read_to_string(&mut stdin).unwrap(); + mustache::compile_str(&stdin) + .and_then(|templ| templ.render_data( + &mut std::io::stdout(), + &data + )).unwrap() +} + +pub fn main() { + from_stdin() +} diff --git a/users/Profpatsch/netencode/netencode.rs b/users/Profpatsch/netencode/netencode.rs new file mode 100644 index 000000000000..5bd43f992f0b --- /dev/null +++ b/users/Profpatsch/netencode/netencode.rs @@ -0,0 +1,844 @@ +extern crate nom; +extern crate exec_helpers; + +use std::collections::HashMap; +use std::io::{Write, Read}; +use std::fmt::{Display, Debug}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum T { + // Unit + Unit, + // Boolean + N1(bool), + // Naturals + N3(u8), + N6(u64), + N7(u128), + // Integers + I3(i8), + I6(i64), + I7(i128), + // Text + // TODO: make into &str + Text(String), + // TODO: rename to Bytes + Binary(Vec<u8>), + // Tags + // TODO: make into &str + // TODO: rename to Tag + Sum(Tag<String, T>), + // TODO: make into &str + Record(HashMap<String, T>), + List(Vec<T>), +} + +impl T { + pub fn to_u<'a>(&'a self) -> U<'a> { + match self { + T::Unit => U::Unit, + T::N1(b) => U::N1(*b), + T::N3(u) => U::N3(*u), + T::N6(u) => U::N6(*u), + T::N7(u) => U::N7(*u), + T::I3(i) => U::I3(*i), + T::I6(i) => U::I6(*i), + T::I7(i) => U::I7(*i), + T::Text(t) => U::Text(t.as_str()), + T::Binary(v) => U::Binary(v), + T::Sum(Tag { tag, val }) => U::Sum( + Tag { tag: tag.as_str(), val: Box::new(val.to_u()) } + ), + T::Record(map) => U::Record( + map.iter().map(|(k, v)| (k.as_str(), v.to_u())).collect() + ), + T::List(l) => U::List( + l.iter().map(|v| v.to_u()).collect::<Vec<U<'a>>>() + ), + } + } + + pub fn encode<'a>(&'a self) -> Vec<u8> { + match self { + // TODO: donโt go via U, inefficient + o => o.to_u().encode() + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum U<'a> { + Unit, + // Boolean + N1(bool), + // Naturals + N3(u8), + N6(u64), + N7(u128), + // Integers + I3(i8), + I6(i64), + I7(i128), + // Text + Text(&'a str), + Binary(&'a [u8]), + // TODO: the U-recursion we do here means we canโt be breadth-lazy anymore + // like we originally planned; maybe we want to go `U<'a>` โ `&'a [u8]` again? + // Tags + // TODO: rename to Tag + Sum(Tag<&'a str, U<'a>>), + Record(HashMap<&'a str, U<'a>>), + List(Vec<U<'a>>), +} + +impl<'a> U<'a> { + pub fn encode(&self) -> Vec<u8> { + let mut c = std::io::Cursor::new(vec![]); + encode(&mut c, self); + c.into_inner() + } + + pub fn to_t(&self) -> T { + match self { + U::Unit => T::Unit, + U::N1(b) => T::N1(*b), + U::N3(u) => T::N3(*u), + U::N6(u) => T::N6(*u), + U::N7(u) => T::N7(*u), + U::I3(i) => T::I3(*i), + U::I6(i) => T::I6(*i), + U::I7(i) => T::I7(*i), + U::Text(t) => T::Text((*t).to_owned()), + U::Binary(v) => T::Binary((*v).to_owned()), + U::Sum(Tag { tag, val }) => T::Sum( + Tag { tag: (*tag).to_owned(), val: Box::new(val.to_t()) } + ), + U::Record(map) => T::Record( + map.iter().map(|(k, v)| ((*k).to_owned(), v.to_t())).collect::<HashMap<String, T>>() + ), + U::List(l) => T::List( + l.iter().map(|v| v.to_t()).collect::<Vec<T>>() + ), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Tag<S, A> { + // TODO: make into &str + pub tag: S, + pub val: Box<A> +} + +impl<S, A> Tag<S, A> { + fn map<F, B>(self, f: F) -> Tag<S, B> + where F: Fn(A) -> B { + Tag { + tag: self.tag, + val: Box::new(f(*self.val)) + } + } +} + +fn encode_tag<W: Write>(w: &mut W, tag: &str, val: &U) -> std::io::Result<()> { + write!(w, "<{}:{}|", tag.len(), tag)?; + encode(w, val)?; + Ok(()) +} + +pub fn encode<W: Write>(w: &mut W, u: &U) -> std::io::Result<()> { + match u { + U::Unit => write!(w, "u,"), + U::N1(b) => if *b { write!(w, "n1:1,") } else { write!(w, "n1:0,") }, + U::N3(n) => write!(w, "n3:{},", n), + U::N6(n) => write!(w, "n6:{},", n), + U::N7(n) => write!(w, "n7:{},", n), + U::I3(i) => write!(w, "i3:{},", i), + U::I6(i) => write!(w, "i6:{},", i), + U::I7(i) => write!(w, "i7:{},", i), + U::Text(s) => { + write!(w, "t{}:", s.len()); + w.write_all(s.as_bytes()); + write!(w, ",") + } + U::Binary(s) => { + write!(w, "b{}:", s.len()); + w.write_all(&s); + write!(w, ",") + }, + U::Sum(Tag{tag, val}) => encode_tag(w, tag, val), + U::Record(m) => { + let mut c = std::io::Cursor::new(vec![]); + for (k, v) in m { + encode_tag(&mut c, k, v)?; + } + write!(w, "{{{}:", c.get_ref().len())?; + w.write_all(c.get_ref())?; + write!(w, "}}") + }, + U::List(l) => { + let mut c = std::io::Cursor::new(vec![]); + for u in l { + encode(&mut c, u)?; + } + write!(w, "[{}:", c.get_ref().len())?; + w.write_all(c.get_ref())?; + write!(w, "]") + } + } +} + +pub fn text(s: String) -> T { + T::Text(s) +} + +pub fn u_from_stdin_or_die_user_error<'a>(prog_name: &'_ str, stdin_buf: &'a mut Vec<u8>) -> U<'a> { + std::io::stdin().lock().read_to_end(stdin_buf); + let u = match parse::u_u(stdin_buf) { + Ok((rest, u)) => match rest { + b"" => u, + _ => exec_helpers::die_user_error(prog_name, format!("stdin contained some soup after netencode value: {:?}", String::from_utf8_lossy(rest))) + }, + Err(err) => exec_helpers::die_user_error(prog_name, format!("unable to parse netencode from stdin: {:?}", err)) + }; + u +} + +pub mod parse { + use super::{T, Tag, U}; + + use std::str::FromStr; + use std::ops::Neg; + use std::collections::HashMap; + + use nom::{IResult}; + use nom::branch::{alt}; + use nom::bytes::streaming::{tag, take}; + use nom::character::streaming::{digit1, char}; + use nom::sequence::{tuple}; + use nom::combinator::{map, map_res, flat_map, map_parser, opt}; + use nom::error::{context, ErrorKind, ParseError}; + + fn unit_t(s: &[u8]) -> IResult<&[u8], ()> { + let (s, _) = context("unit", tag("u,"))(s)?; + Ok((s, ())) + } + + fn usize_t(s: &[u8]) -> IResult<&[u8], usize> { + context( + "usize", + map_res( + map_res(digit1, |n| std::str::from_utf8(n)), + |s| s.parse::<usize>()) + )(s) + } + + fn sized(begin: char, end: char) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> { + move |s: &[u8]| { + // This is the point where we check the descriminator; + // if the beginning char does not match, we can immediately return. + let (s, _) = char(begin)(s)?; + let (s, (len, _)) = tuple(( + usize_t, + char(':') + ))(s)?; + let (s, (res, _)) = tuple(( + take(len), + char(end) + ))(s)?; + Ok((s, res)) + } + } + + + fn uint_t<'a, I: FromStr + 'a>(t: &'static str) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], I> { + move |s: &'a [u8]| { + let (s, (_, _, int, _)) = tuple(( + tag(t.as_bytes()), + char(':'), + map_res( + map_res(digit1, |n: &[u8]| std::str::from_utf8(n)), + |s| s.parse::<I>() + ), + char(',') + ))(s)?; + Ok((s, int)) + } + } + + fn bool_t<'a>() -> impl Fn(&'a [u8]) -> IResult<&'a [u8], bool> { + context("bool", alt(( + map(tag("n1:0,"), |_| false), + map(tag("n1:1,"), |_| true), + ))) + } + + fn int_t<'a, I: FromStr + Neg<Output=I>>(t: &'static str) -> impl Fn(&'a [u8]) -> IResult<&[u8], I> { + context( + t, + move |s: &'a [u8]| { + let (s, (_, _, neg, int, _)) = tuple(( + tag(t.as_bytes()), + char(':'), + opt(char('-')), + map_res( + map_res(digit1, |n: &[u8]| std::str::from_utf8(n)), + |s| s.parse::<I>() + ), + char(',') + ))(s)?; + let res = match neg { + Some(_) => -int, + None => int, + }; + Ok((s, res)) + } + ) + } + + fn tag_t(s: &[u8]) -> IResult<&[u8], Tag<String, T>> { + // recurses into the main parser + map(tag_g(t_t), + |Tag {tag, val}| + Tag { + tag: tag.to_string(), + val + })(s) + } + + fn tag_g<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Tag<&'a str, O>> + where + P: Fn(&'a [u8]) -> IResult<&'a [u8], O> + { + move |s: &[u8]| { + let (s, tag) = sized('<', '|')(s)?; + let (s, val) = inner(s)?; + Ok((s, Tag { + tag: std::str::from_utf8(tag) + .map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?, + val: Box::new(val) + })) + + } + } + + /// parse text scalar (`t5:hello,`) + fn text(s: &[u8]) -> IResult<&[u8], T> { + let (s, res) = text_g(s)?; + Ok((s, T::Text(res.to_string()))) + } + + fn text_g(s: &[u8]) -> IResult<&[u8], &str> { + let (s, res) = sized('t', ',')(s)?; + Ok((s, + std::str::from_utf8(res) + .map_err(|_| nom::Err::Failure((s, ErrorKind::Char)))?, + )) + } + + fn binary<'a>() -> impl Fn(&'a [u8]) -> IResult<&'a [u8], T> { + map(binary_g(), |b| T::Binary(b.to_owned())) + } + + fn binary_g() -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> { + sized('b', ',') + } + + fn list_t(s: &[u8]) -> IResult<&[u8], Vec<T>> { + list_g(t_t)(s) + } + + /// Wrap the inner parser of an `many0`/`fold_many0`, so that the parser + /// is not called when the `s` is empty already, preventing it from + /// returning `Incomplete` on streaming parsing. + fn inner_no_empty_string<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], O> + where + O: Clone, + P: Fn(&'a [u8]) -> IResult<&'a [u8], O>, + { + move |s: &'a [u8]| { + if s.is_empty() { + // This is a bit hacky, `many0` considers the inside done + // when a parser returns `Err::Error`, ignoring the actual error content + Err(nom::Err::Error((s, nom::error::ErrorKind::Many0))) + } else { + inner(s) + } + } + } + + fn list_g<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Vec<O>> + where + O: Clone, + P: Fn(&'a [u8]) -> IResult<&'a [u8], O>, + { + map_parser( + sized('[', ']'), + nom::multi::many0(inner_no_empty_string(inner)) + ) + } + + fn record_t<'a>(s: &'a [u8]) -> IResult<&'a [u8], HashMap<String, T>> { + let (s, r) = record_g(t_t)(s)?; + Ok((s, + r.into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect::<HashMap<_,_>>())) + } + + fn record_g<'a, P, O>(inner: P) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], HashMap<&'a str, O>> + where + O: Clone, + P: Fn(&'a [u8]) -> IResult<&'a [u8], O> + { + move |s: &'a [u8]| { + let (s, map) = map_parser( + sized('{', '}'), + nom::multi::fold_many0( + inner_no_empty_string(tag_g(&inner)), + HashMap::new(), + |mut acc: HashMap<_,_>, Tag { tag, mut val }| { + // ignore duplicated tag names that appear later + // according to netencode spec + if ! acc.contains_key(tag) { + acc.insert(tag, *val); + } + acc + } + ) + )(s)?; + if map.is_empty() { + // records must not be empty, according to the spec + Err(nom::Err::Failure((s,nom::error::ErrorKind::Many1))) + } else { + Ok((s, map)) + } + } + } + + pub fn u_u(s: &[u8]) -> IResult<&[u8], U> { + alt(( + map(text_g, U::Text), + map(binary_g(), U::Binary), + map(unit_t, |()| U::Unit), + map(tag_g(u_u), |t| U::Sum(t)), + map(list_g(u_u), U::List), + map(record_g(u_u), U::Record), + + map(bool_t(), |u| U::N1(u)), + map(uint_t("n3"), |u| U::N3(u)), + map(uint_t("n6"), |u| U::N6(u)), + map(uint_t("n7"), |u| U::N7(u)), + map(int_t("i3"), |u| U::I3(u)), + map(int_t("i6"), |u| U::I6(u)), + map(int_t("i7"), |u| U::I7(u)), + + // less common + map(uint_t("n2"), |u| U::N3(u)), + map(uint_t("n4"), |u| U::N6(u)), + map(uint_t("n5"), |u| U::N6(u)), + map(int_t("i1"), |u| U::I3(u)), + map(int_t("i2"), |u| U::I3(u)), + map(int_t("i4"), |u| U::I6(u)), + map(int_t("i5"), |u| U::I6(u)), + // TODO: 8, 9 not supported + ))(s) + } + + pub fn t_t(s: &[u8]) -> IResult<&[u8], T> { + alt(( + text, + binary(), + map(unit_t, |_| T::Unit), + map(tag_t, |t| T::Sum(t)), + map(list_t, |l| T::List(l)), + map(record_t, |p| T::Record(p)), + + map(bool_t(), |u| T::N1(u)), + // 8, 64 and 128 bit + map(uint_t("n3"), |u| T::N3(u)), + map(uint_t("n6"), |u| T::N6(u)), + map(uint_t("n7"), |u| T::N7(u)), + map(int_t("i3"), |u| T::I3(u)), + map(int_t("i6"), |u| T::I6(u)), + map(int_t("i7"), |u| T::I7(u)), + + // less common + map(uint_t("n2"), |u| T::N3(u)), + map(uint_t("n4"), |u| T::N6(u)), + map(uint_t("n5"), |u| T::N6(u)), + map(int_t("i1"), |u| T::I3(u)), + map(int_t("i2"), |u| T::I3(u)), + map(int_t("i4"), |u| T::I6(u)), + map(int_t("i5"), |u| T::I6(u)), + // TODO: 8, 9 not supported + ))(s) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_parse_unit_t() { + assert_eq!( + unit_t("u,".as_bytes()), + Ok(("".as_bytes(), ())) + ); + } + + #[test] + fn test_parse_bool_t() { + assert_eq!( + bool_t()("n1:0,".as_bytes()), + Ok(("".as_bytes(), false)) + ); + assert_eq!( + bool_t()("n1:1,".as_bytes()), + Ok(("".as_bytes(), true)) + ); + } + + #[test] + fn test_parse_usize_t() { + assert_eq!( + usize_t("32foo".as_bytes()), + Ok(("foo".as_bytes(), 32)) + ); + } + + #[test] + fn test_parse_int_t() { + assert_eq!( + uint_t::<u8>("n3")("n3:42,abc".as_bytes()), + Ok(("abc".as_bytes(), 42)) + ); + assert_eq!( + uint_t::<u8>("n3")("n3:1024,abc".as_bytes()), + Err(nom::Err::Error(("1024,abc".as_bytes(), nom::error::ErrorKind::MapRes))) + ); + assert_eq!( + int_t::<i64>("i6")("i6:-23,abc".as_bytes()), + Ok(("abc".as_bytes(), -23)) + ); + assert_eq!( + int_t::<i128>("i3")("i3:0,:abc".as_bytes()), + Ok((":abc".as_bytes(), 0)) + ); + assert_eq!( + uint_t::<u8>("n7")("n7:09,".as_bytes()), + Ok(("".as_bytes(), 9)) + ); + // assert_eq!( + // length("c"), + // Err(nom::Err::Error(("c", nom::error::ErrorKind::Digit))) + // ); + // assert_eq!( + // length(":"), + // Err(nom::Err::Error((":", nom::error::ErrorKind::Digit))) + // ); + } + + #[test] + fn test_parse_text() { + assert_eq!( + text("t5:hello,".as_bytes()), + Ok(("".as_bytes(), T::Text("hello".to_owned()))), + "{}", r"t5:hello," + ); + assert_eq!( + text("t4:fo".as_bytes()), + // The content of the text should be 4 long + Err(nom::Err::Incomplete(nom::Needed::Size(4))), + "{}", r"t4:fo," + ); + assert_eq!( + text("t9:ไปๆฅใฏ,".as_bytes()), + Ok(("".as_bytes(), T::Text("ไปๆฅใฏ".to_owned()))), + "{}", r"t9:ไปๆฅใฏ," + ); + } + + #[test] + fn test_parse_binary() { + assert_eq!( + binary()("b5:hello,".as_bytes()), + Ok(("".as_bytes(), T::Binary(Vec::from("hello".to_owned())))), + "{}", r"b5:hello," + ); + assert_eq!( + binary()("b4:fo".as_bytes()), + // The content of the byte should be 4 long + Err(nom::Err::Incomplete(nom::Needed::Size(4))), + "{}", r"b4:fo," + ); + assert_eq!( + binary()("b4:foob".as_bytes()), + // The content is 4 bytes now, but the finishing , is missing + Err(nom::Err::Incomplete(nom::Needed::Size(1))), + "{}", r"b4:fo," + ); + assert_eq!( + binary()("b9:ไปๆฅใฏ,".as_bytes()), + Ok(("".as_bytes(), T::Binary(Vec::from("ไปๆฅใฏ".as_bytes())))), + "{}", r"b9:ไปๆฅใฏ," + ); + } + + #[test] + fn test_list() { + assert_eq!( + list_t("[0:]".as_bytes()), + Ok(("".as_bytes(), vec![])), + "{}", r"[0:]" + ); + assert_eq!( + list_t("[6:u,u,u,]".as_bytes()), + Ok(("".as_bytes(), vec![ + T::Unit, + T::Unit, + T::Unit, + ])), + "{}", r"[6:u,u,u,]" + ); + assert_eq!( + list_t("[15:u,[7:t3:foo,]u,]".as_bytes()), + Ok(("".as_bytes(), vec![ + T::Unit, + T::List(vec![T::Text("foo".to_owned())]), + T::Unit, + ])), + "{}", r"[15:u,[7:t3:foo,]u,]" + ); + } + + #[test] + fn test_record() { + assert_eq!( + record_t("{21:<1:a|u,<1:b|u,<1:c|u,}".as_bytes()), + Ok(("".as_bytes(), vec![ + ("a".to_owned(), T::Unit), + ("b".to_owned(), T::Unit), + ("c".to_owned(), T::Unit), + ].into_iter().collect::<HashMap<String, T>>())), + "{}", r"{21:<1:a|u,<1:b|u,<1:c|u,}" + ); + // duplicated keys are ignored (first is taken) + assert_eq!( + record_t("{25:<1:a|u,<1:b|u,<1:a|i1:-1,}".as_bytes()), + Ok(("".as_bytes(), vec![ + ("a".to_owned(), T::Unit), + ("b".to_owned(), T::Unit), + ].into_iter().collect::<HashMap<_,_>>())), + "{}", r"{25:<1:a|u,<1:b|u,<1:a|i1:-1,}" + ); + // empty records are not allowed + assert_eq!( + record_t("{0:}".as_bytes()), + Err(nom::Err::Failure(("".as_bytes(), nom::error::ErrorKind::Many1))), + "{}", r"{0:}" + ); + } + + #[test] + fn test_parse() { + assert_eq!( + t_t("n3:255,".as_bytes()), + Ok(("".as_bytes(), T::N3(255))), + "{}", r"n3:255," + ); + assert_eq!( + t_t("t6:halloo,".as_bytes()), + Ok(("".as_bytes(), T::Text("halloo".to_owned()))), + "{}", r"t6:halloo," + ); + assert_eq!( + t_t("<3:foo|t6:halloo,".as_bytes()), + Ok(("".as_bytes(), T::Sum (Tag { + tag: "foo".to_owned(), + val: Box::new(T::Text("halloo".to_owned())) + }))), + "{}", r"<3:foo|t6:halloo," + ); + // { a: Unit + // , foo: List <A: Unit | B: List i3> } + assert_eq!( + t_t("{52:<1:a|u,<3:foo|[33:<1:A|u,<1:A|n1:1,<1:B|[7:i3:127,]]}".as_bytes()), + Ok(("".as_bytes(), T::Record(vec![ + ("a".to_owned(), T::Unit), + ("foo".to_owned(), T::List(vec![ + T::Sum(Tag { tag: "A".to_owned(), val: Box::new(T::Unit) }), + T::Sum(Tag { tag: "A".to_owned(), val: Box::new(T::N1(true)) }), + T::Sum(Tag { tag: "B".to_owned(), val: Box::new(T::List(vec![T::I3(127)])) }), + ])) + ].into_iter().collect::<HashMap<String, T>>()))), + "{}", r"{52:<1:a|u,<3:foo|[33:<1:A|u,<1:A|n1:1,<1:B|[7:i3:127,]]}" + ); + } + + } +} + +pub mod dec { + use super::*; + use std::collections::HashMap; + + pub struct DecodeError(pub String); + + pub trait Decoder<'a> { + type A; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError>; + } + + /// Any netencode, as `T`. + #[derive(Clone, Copy)] + pub struct AnyT; + /// Any netencode, as `U`. + #[derive(Clone, Copy)] + pub struct AnyU; + + impl<'a> Decoder<'a> for AnyT { + type A = T; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + Ok(u.to_t()) + } + } + + impl<'a> Decoder<'a> for AnyU { + type A = U<'a>; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + Ok(u) + } + } + + /// A text + #[derive(Clone, Copy)] + pub struct Text; + + /// A bytestring + // TODO: rename to Bytes + #[derive(Clone, Copy)] + pub struct Binary; + + impl<'a> Decoder<'a> for Text { + type A = &'a str; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match u { + U::Text(t) => Ok(t), + other => Err(DecodeError(format!("Cannot decode {:?} into Text", other))), + } + } + } + + impl<'a> Decoder<'a> for Binary { + type A = &'a [u8]; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match u { + U::Binary(b) => Ok(b), + other => Err(DecodeError(format!("Cannot decode {:?} into Binary", other))), + } + } + } + + /// Any scalar, converted to bytes. + #[derive(Clone, Copy)] + pub struct ScalarAsBytes; + + impl<'a> Decoder<'a> for ScalarAsBytes { + type A = Vec<u8>; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match u { + U::N3(u) => Ok(format!("{}", u).into_bytes()), + U::N6(u) => Ok(format!("{}", u).into_bytes()), + U::N7(u) => Ok(format!("{}", u).into_bytes()), + U::I3(i) => Ok(format!("{}", i).into_bytes()), + U::I6(i) => Ok(format!("{}", i).into_bytes()), + U::I7(i) => Ok(format!("{}", i).into_bytes()), + U::Text(t) => Ok(t.as_bytes().to_owned()), + U::Binary(b) => Ok(b.to_owned()), + o => Err(DecodeError(format!("Cannot decode {:?} into scalar", o))), + } + } + } + + /// A map of Ts (TODO: rename to map) + #[derive(Clone, Copy)] + pub struct Record<T>(pub T); + + impl<'a, Inner> Decoder<'a> for Record<Inner> + where Inner: Decoder<'a> + { + type A = HashMap<&'a str, Inner::A>; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match u { + U::Record(map) => + map.into_iter() + .map(|(k, v)| self.0.dec(v).map(|v2| (k, v2))) + .collect::<Result<Self::A, _>>(), + o => Err(DecodeError(format!("Cannot decode {:?} into record", o))) + } + } + } + + /// Assume a record and project out the field with the given name and type. + #[derive(Clone, Copy)] + pub struct RecordDot<'a, T> { + pub field: &'a str, + pub inner: T + } + + impl <'a, Inner> Decoder<'a> for RecordDot<'_, Inner> + where Inner: Decoder<'a> + Clone + { + type A = Inner::A; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match Record(self.inner.clone()).dec(u) { + Ok(mut map) => match map.remove(self.field) { + Some(inner) => Ok(inner), + None => Err(DecodeError(format!("Cannot find `{}` in record map", self.field))), + }, + Err(err) => Err(err), + } + } + } + + /// Equals one of the listed `A`s exactly, after decoding. + #[derive(Clone)] + pub struct OneOf<T, A>{ + pub inner: T, + pub list: Vec<A>, + } + + impl <'a, Inner> Decoder<'a> for OneOf<Inner, Inner::A> + where Inner: Decoder<'a>, + Inner::A: Display + Debug + PartialEq + { + type A = Inner::A; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match self.inner.dec(u) { + Ok(inner) => match self.list.iter().any(|x| x.eq(&inner)) { + true => Ok(inner), + false => Err(DecodeError(format!("{} is not one of {:?}", inner, self.list))) + }, + Err(err) => Err(err) + } + } + } + + /// Try decoding as `T`. + #[derive(Clone)] + pub struct Try<T>(pub T); + + impl <'a, Inner> Decoder<'a> for Try<Inner> + where Inner: Decoder<'a> + { + type A = Option<Inner::A>; + fn dec(&self, u: U<'a>) -> Result<Self::A, DecodeError> { + match self.0.dec(u) { + Ok(inner) => Ok(Some(inner)), + Err(err) => Ok(None) + } + } + } + +} diff --git a/users/Profpatsch/netencode/pretty.rs b/users/Profpatsch/netencode/pretty.rs new file mode 100644 index 000000000000..8fec24a60e1a --- /dev/null +++ b/users/Profpatsch/netencode/pretty.rs @@ -0,0 +1,140 @@ +extern crate netencode; + +use netencode::{U, T, Tag}; + +pub enum Pretty { + Single { + r#type: char, + length: String, + val: String, + trailer: char, + }, + Tag { + r#type: char, + length: String, + key: String, + inner: char, + val: Box<Pretty>, + }, + Multi { + r#type: char, + length: String, + vals: Vec<Pretty>, + trailer: char + }, +} + +impl Pretty { + pub fn from_u<'a>(u: U<'a>) -> Pretty { + match u { + U::Unit => Self::scalar('u', "", ""), + U::N1(b) => Self::scalar('n', "1:", if b { "1" } else { "0" }), + U::N3(n) => Self::scalar('n', "3:", n), + U::N6(n) => Self::scalar('n', "6:", n), + U::N7(n) => Self::scalar('n', "7:", n), + U::I3(i) => Self::scalar('i', "3:", i), + U::I6(i) => Self::scalar('i', "6:", i), + U::I7(i) => Self::scalar('i', "7:", i), + U::Text(s) => Pretty::Single { + r#type: 't', + length: format!("{}:", s.len()), + val: s.to_string(), + trailer: ',' + }, + U::Binary(s) => Pretty::Single { + r#type: 'b', + length: format!("{}:", s.len()), + // For pretty printing we want the string to be visible obviously. + // Instead of not supporting binary, letโs use lossy conversion. + val: String::from_utf8_lossy(s).into_owned(), + trailer: ',' + }, + U::Sum(Tag{tag, val}) => Self::pretty_tag(tag, Self::from_u(*val)), + U::Record(m) => Pretty::Multi { + r#type: '{', + // TODO: we are losing the size here, should we recompute it? Keep it? + length: String::from(""), + vals: m.into_iter().map(|(k, v)| Self::pretty_tag(k, Self::from_u(v))).collect(), + trailer: '}' + }, + U::List(l) => Pretty::Multi { + r#type: '[', + // TODO: we are losing the size here, should we recompute it? Keep it? + length: String::from(""), + vals: l.into_iter().map(|v| Self::from_u(v)).collect(), + trailer: ']', + }, + } + } + + fn scalar<D>(r#type: char, length: &str, d: D) -> Pretty + where D: std::fmt::Display + { + Pretty::Single { + r#type, + length: length.to_string(), + val: format!("{}", d), + trailer: ',' + } + } + + fn pretty_tag(tag: &str, val: Pretty) -> Pretty { + Pretty::Tag { + r#type: '<', + length: format!("{}:", tag.len()), + key: tag.to_string(), + inner: '|', + val: Box::new(val), + } + } + + pub fn print_multiline<W>(&self, mut w: &mut W) -> std::io::Result<()> + where W: std::io::Write + { + Self::go(&mut w, self, 0, true); + write!(w, "\n") + } + + fn go<W>(mut w: &mut W, p: &Pretty, depth: usize, is_newline: bool) -> std::io::Result<()> + where W: std::io::Write + { + const full : usize = 4; + const half : usize = 2; + let i = &vec![b' '; depth*full]; + let iandhalf = &vec![b' '; depth*full + half]; + let (i, iandhalf) = unsafe {( + std::str::from_utf8_unchecked(i), + std::str::from_utf8_unchecked(iandhalf), + )}; + if is_newline { + write!(&mut w, "{}", i); + } + match p { + Pretty::Single {r#type, length, val, trailer} => + write!(&mut w, "{} {}{}", r#type, val, trailer), + Pretty::Tag { r#type, length, key, inner, val } => { + write!(&mut w, "{} {} {}", r#type, key, inner)?; + Self::go::<W>(&mut w, val, depth, false) + }, + // if the length is 0 or 1, we print on one line, + // only if thereโs more than one element we split the resulting value. + // we never break lines on arbitrary column sizes, since that is just silly. + Pretty::Multi {r#type, length, vals, trailer} => match vals.len() { + 0 => write!(&mut w, "{} {}", r#type, trailer), + 1 => { + write!(&mut w, "{} ", r#type); + Self::go::<W>(&mut w, &vals[0], depth, false)?; + write!(&mut w, "{}", trailer) + }, + more => { + write!(&mut w, "\n{}{} \n", iandhalf, r#type)?; + for v in vals { + Self::go::<W>(&mut w, v, depth + 1, true)?; + write!(&mut w, "\n")?; + } + write!(&mut w, "{}{}", iandhalf, trailer) + } + }, + } + } +} diff --git a/users/Profpatsch/netstring/README.md b/users/Profpatsch/netstring/README.md new file mode 100644 index 000000000000..b8daea11d158 --- /dev/null +++ b/users/Profpatsch/netstring/README.md @@ -0,0 +1,18 @@ +# Netstring + +Netstrings are a djb invention. They are intended as a serialization format. Instead of inline control characters like `\n` or `\0` to signal the end of a string, they use a run-length encoding given as the number of bytes, encoded in ASCII, at the beginning of the string. + +``` +hello -> 5:hello, +foo! -> 4:foo!, +ใใใซใกใฏ -> 15:ใใใซใกใฏ, +``` + +They can be used to encode e.g. lists by simply concatenating and reading them in one-by-one. + +If you need a more complex encoding, you could start encoding e.g. tuples as netstrings-in-netstrings, or you could use [`netencode`](../netencode/README.md) instead, which is what-if-json-but-netstrings, and takes the idea of netstrings to their logical conclusion. + +Resources: + +Spec: http://cr.yp.to/proto/netstrings.txt +Wiki: https://en.wikipedia.org/wiki/Netstring diff --git a/users/Profpatsch/netstring/default.nix b/users/Profpatsch/netstring/default.nix new file mode 100644 index 000000000000..dcc29d7e6fec --- /dev/null +++ b/users/Profpatsch/netstring/default.nix @@ -0,0 +1,63 @@ +{ lib, pkgs, depot, ... }: +let + toNetstring = s: + "${toString (builtins.stringLength s)}:${s},"; + + toNetstringList = xs: + lib.concatStrings (map toNetstring xs); + + toNetstringKeyVal = attrs: + lib.concatStrings + (lib.mapAttrsToList + (k: v: toNetstring (toNetstring k + toNetstring v)) + attrs); + + python-netstring = depot.users.Profpatsch.writers.python3Lib { + name = "netstring"; + } '' + def read_netstring(bytes): + (int_length, rest) = bytes.split(sep=b':', maxsplit=1) + val = rest[:int(int_length)] + # has to end on a , + assert(rest[len(val)] == ord(',')) + return (val, rest[len(val) + 1:]) + + def read_netstring_key_val(bytes): + (keyvalnet, rest) = read_netstring(bytes) + (key, valnet) = read_netstring(keyvalnet) + (val, nothing) = read_netstring(valnet) + assert(nothing == b"") + return (key, val, rest) + + def read_netstring_key_val_list(bytes): + rest = bytes + res = {} + while rest != b"": + (key, val, r) = read_netstring_key_val(rest) + rest = r + res[key] = val + return res + ''; + + rust-netstring = depot.nix.writers.rustSimpleLib { + name = "netstring"; + } '' + pub fn to_netstring(s: &[u8]) -> Vec<u8> { + let len = s.len(); + // length of the integer as ascii + let i_len = ((len as f64).log10() as usize) + 1; + let ns_len = i_len + 1 + len + 1; + let mut res = Vec::with_capacity(ns_len); + res.extend_from_slice(format!("{}:", len).as_bytes()); + res.extend_from_slice(s); + res.push(b','); + res + } + ''; + +in depot.nix.readTree.drvTargets { + inherit + python-netstring + rust-netstring + ; +} diff --git a/users/Profpatsch/netstring/tests/default.nix b/users/Profpatsch/netstring/tests/default.nix new file mode 100644 index 000000000000..710ba3d30526 --- /dev/null +++ b/users/Profpatsch/netstring/tests/default.nix @@ -0,0 +1,61 @@ +{ depot, lib, pkgs, ... }: + +let + + python-netstring-test = depot.users.Profpatsch.writers.python3 { + name = "python-netstring-test"; + libraries = p: [ + depot.users.Profpatsch.netstring.python-netstring + ]; + } '' + import netstring + + def assEq(left, right): + assert left == right, "{} /= {}".format(str(left), str(right)) + + assEq( + netstring.read_netstring(b"""${depot.nix.netstring.fromString "hi!"}"""), + (b"hi!", b"") + ) + + assEq( + netstring.read_netstring_key_val( + b"""${depot.nix.netstring.attrsToKeyValList { foo = "42"; }}""" + ), + (b'foo', b'42', b"") + ) + + assEq( + netstring.read_netstring_key_val_list( + b"""${depot.nix.netstring.attrsToKeyValList { foo = "42"; bar = "hi"; }}""" + ), + { b'foo': b'42', b'bar': b'hi' } + ) + ''; + + rust-netstring-test = depot.nix.writers.rustSimple { + name = "rust-netstring-test"; + dependencies = [ + depot.users.Profpatsch.netstring.rust-netstring + ]; + } '' + extern crate netstring; + + fn main() { + assert_eq!( + std::str::from_utf8(&netstring::to_netstring(b"hello")).unwrap(), + r##"${depot.nix.netstring.fromString "hello"}"## + ); + assert_eq!( + std::str::from_utf8(&netstring::to_netstring("ใใใซใกใฏ".as_bytes())).unwrap(), + r##"${depot.nix.netstring.fromString "ใใใซใกใฏ"}"## + ); + } + ''; + +in depot.nix.readTree.drvTargets { + inherit + python-netstring-test + rust-netstring-test + ; +} diff --git a/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs b/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs new file mode 100644 index 000000000000..3ed96a7b6eac --- /dev/null +++ b/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs @@ -0,0 +1,80 @@ +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} +import Nix.Parser +import Nix.Expr.Types +import Nix.Expr.Types.Annotated +import System.Environment (getArgs) +import System.Exit (die) +import Data.Fix (Fix(..)) +import qualified Data.Text as Text +import qualified Data.ByteString.Lazy.Char8 as BL +import qualified Data.Aeson as A +import qualified Data.Aeson.Encoding as A +import Data.Function ((&)) +import qualified System.IO as IO +import qualified Text.Megaparsec.Pos as MP + +main = do + (nixFile:_) <- getArgs + (parseNixFileLoc nixFile :: IO _) >>= \case + Failure err -> do + ePutStrLn $ show err + die "oh no" + Success expr -> do + case snd $ match expr of + NoArguments -> do + ePutStrLn $ "NoArguments in " <> nixFile + printPairs mempty + YesLib vars -> do + ePutStrLn $ "lib in " <> show vars <> " in " <> nixFile + printPairs mempty + NoLib vars srcSpan -> do + ePutStrLn $ nixFile <> " needs lib added" + printPairs + $ "fileName" A..= nixFile + <> "fromLine" A..= (srcSpan & spanBegin & sourceLine) + <> "fromColumn" A..= (srcSpan & spanBegin & sourceColumn) + <> "toLine" A..= (srcSpan & spanEnd & sourceLine) + <> "toColumn" A..= (srcSpan & spanEnd & sourceColumn) + +printPairs pairs = BL.putStrLn $ A.encodingToLazyByteString $ A.pairs pairs + +ePutStrLn = IO.hPutStrLn IO.stderr + +data Descend = YesDesc | NoDesc + deriving Show +data Matched = NoArguments | NoLib [VarName] SrcSpan | YesLib [VarName] + deriving Show + +match :: Fix (Compose (Ann SrcSpan) NExprF) -> (Descend, Matched) +match = \case + (AnnE outerSpan (NAbs (ParamSet params _ _) (AnnE innerSpan _))) -> (NoDesc, + let vars = map fst params in + case (any (== "lib") vars) of + True -> YesLib vars + False -> + -- The span of the arglist is from the beginning of the match + -- to the beginning of the inner expression + let varSpan = SrcSpan + { spanBegin = outerSpan & spanBegin + -- -1 to prevent the spans from overlapping + , spanEnd = sourcePosMinus1 (innerSpan & spanBegin) } + in NoLib vars varSpan) + _ -> (NoDesc, NoArguments) + +-- | Remove one from a source positon. +-- +-- That means if the current position is at the very beginning of a line, +-- jump to the previous line. +sourcePosMinus1 :: SourcePos -> SourcePos +sourcePosMinus1 src@(SourcePos { sourceLine, sourceColumn }) = + let + col = MP.mkPos $ max (MP.unPos sourceColumn - 1) 1 + line = MP.mkPos $ case MP.unPos sourceColumn of + 1 -> max (MP.unPos sourceLine - 1) 1 + _ -> MP.unPos sourceLine + in src + { sourceLine = line + , sourceColumn = col } diff --git a/users/Profpatsch/nixpkgs-rewriter/default.nix b/users/Profpatsch/nixpkgs-rewriter/default.nix new file mode 100644 index 000000000000..787162d4973a --- /dev/null +++ b/users/Profpatsch/nixpkgs-rewriter/default.nix @@ -0,0 +1,112 @@ +{ depot, pkgs, ... }: +let + inherit (depot.nix) + writeExecline + ; + inherit (depot.users.Profpatsch.lib) + debugExec + ; + + bins = depot.nix.getBins pkgs.coreutils [ "head" "shuf" ] + // depot.nix.getBins pkgs.jq [ "jq" ] + // depot.nix.getBins pkgs.findutils [ "xargs" ] + // depot.nix.getBins pkgs.gnused [ "sed" ] + ; + + export-json-object = pkgs.writers.writePython3 "export-json-object" {} '' + import json + import sys + import os + + d = json.load(sys.stdin) + + if d == {}: + sys.exit(0) + + for k, v in d.items(): + os.environ[k] = str(v) + + os.execvp(sys.argv[1], sys.argv[1:]) + ''; + + meta-stdenv-lib = pkgs.writers.writeHaskell "meta-stdenv-lib" { + libraries = [ + pkgs.haskellPackages.hnix + pkgs.haskellPackages.aeson + ]; + } ./MetaStdenvLib.hs; + + replace-between-lines = writeExecline "replace-between-lines" { readNArgs = 1; } [ + "importas" "-ui" "file" "fileName" + "importas" "-ui" "from" "fromLine" + "importas" "-ui" "to" "toLine" + "if" [ depot.tools.eprintf "%s-%s\n" "$from" "$to" ] + (debugExec "adding lib") + bins.sed + "-e" "\${from},\${to} \${1}" + "-i" "$file" + ]; + + add-lib-if-necessary = writeExecline "add-lib-if-necessary" { readNArgs = 1; } [ + "pipeline" [ meta-stdenv-lib "$1" ] + export-json-object + # first replace any stdenv.lib mentions in the arg header + # if this is not done, the replace below kills these. + # Since we want it anyway ultimately, letโs do it here. + "if" [ replace-between-lines "s/stdenv\.lib/lib/" ] + # then add the lib argument + # (has to be before stdenv, otherwise default arguments might be in the way) + replace-between-lines "s/stdenv/lib, stdenv/" + ]; + + metaString = ''meta = with stdenv.lib; {''; + + replace-stdenv-lib = pkgs.writers.writeBash "replace-stdenv-lib" '' + set -euo pipefail + sourceDir="$1" + for file in $( + ${pkgs.ripgrep}/bin/rg \ + --files-with-matches \ + --fixed-strings \ + -e '${metaString}' \ + "$sourceDir" + ) + do + echo "replacing stdenv.lib meta in $file" >&2 + ${bins.sed} -e '/${metaString}/ s/stdenv.lib/lib/' \ + -i "$file" + ${add-lib-if-necessary} "$file" + done + ''; + + instantiate-nixpkgs-randomly = writeExecline "instantiate-nixpkgs-randomly" { readNArgs = 1; } [ + "export" "NIXPKGS_ALLOW_BROKEN" "1" + "export" "NIXPKGS_ALLOW_UNFREE" "1" + "export" "NIXPKGS_ALLOW_INSECURE" "1" + "export" "NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM" "1" + "pipeline" [ + "nix" + "eval" + "--raw" + ''( + let pkgs = import ''${1} {}; + in builtins.toJSON (builtins.attrNames pkgs) + )'' + ] + "pipeline" [ bins.jq "-r" ".[]" ] + "pipeline" [ bins.shuf ] + "pipeline" [ bins.head "-n" "1000" ] + bins.xargs "-I" "{}" "-n1" + "if" [ depot.tools.eprintf "instantiating %s\n" "{}" ] + "nix-instantiate" "$1" "-A" "{}" + ]; + +in depot.nix.readTree.drvTargets { + inherit + instantiate-nixpkgs-randomly + # requires hnix, which we donโt want in tvl for now + # uncomment manually if you want to use it. + # meta-stdenv-lib + # replace-stdenv-lib + ; +} diff --git a/users/Profpatsch/read-http.nix b/users/Profpatsch/read-http.nix new file mode 100644 index 000000000000..854a11b7d099 --- /dev/null +++ b/users/Profpatsch/read-http.nix @@ -0,0 +1,16 @@ +{ depot, pkgs, ... }: + +let + + read-http = depot.nix.writers.rustSimple { + name = "read-http"; + dependencies = [ + depot.third_party.rust-crates.ascii + depot.third_party.rust-crates.httparse + depot.users.Profpatsch.netencode.netencode-rs + depot.users.Profpatsch.arglib.netencode.rust + depot.users.Profpatsch.execline.exec-helpers + ]; + } (builtins.readFile ./read-http.rs); + +in read-http diff --git a/users/Profpatsch/read-http.rs b/users/Profpatsch/read-http.rs new file mode 100644 index 000000000000..50ff663b994a --- /dev/null +++ b/users/Profpatsch/read-http.rs @@ -0,0 +1,211 @@ +extern crate httparse; +extern crate netencode; +extern crate arglib_netencode; +extern crate ascii; +extern crate exec_helpers; + +use std::os::unix::io::FromRawFd; +use std::io::Read; +use std::io::Write; +use std::collections::HashMap; +use exec_helpers::{die_user_error, die_expected_error, die_temporary}; + +use netencode::{U, T, dec}; +use netencode::dec::Decoder; + +enum What { + Request, + Response +} + +// reads a http request (stdin), and writes all headers to stdout, as netencoded record. +// The keys are text, but can be lists of text iff headers appear multiple times, so beware. +fn main() -> std::io::Result<()> { + + exec_helpers::no_args("read-http"); + + let args = dec::RecordDot { + field: "what", + inner: dec::OneOf { + list: vec!["request", "response"], + inner: dec::Text + } + }; + let what : What = match args.dec(arglib_netencode::arglib_netencode("read-http", None).to_u()) { + Ok("request") => What::Request, + Ok("response") => What::Response, + Ok(v) => panic!("shouldnโt happen!, value was: {}", v), + Err(dec::DecodeError(err)) => die_user_error("read-http", err), + }; + + fn read_stdin_to_complete<F>(mut parse: F) -> () + where F: FnMut(&[u8]) -> httparse::Result<usize> + { + let mut res = httparse::Status::Partial; + loop { + if let httparse::Status::Complete(_) = res { + return; + } + let mut buf = [0; 2048]; + match std::io::stdin().read(&mut buf[..]) { + Ok(size) => if size == 0 { + break; + }, + Err(err) => die_temporary("read-http", format!("could not read from stdin, {:?}", err)) + } + match parse(&buf) { + Ok(status) => { + res = status; + } + Err(err) => die_temporary("read-http", format!("httparse parsing failed: {:#?}", err)) + } + } + } + + fn normalize_headers<'a>(headers: &'a [httparse::Header]) -> HashMap<String, U<'a>> { + let mut res = HashMap::new(); + for httparse::Header { name, value } in headers { + let val = ascii::AsciiStr::from_ascii(*value) + .expect(&format!("read-http: we require header values to be ASCII, but the header {} was {:?}", name, value)) + .as_str(); + // lowercase the header names, since the standard doesnโt care + // and we want unique strings to match against + let name_lower = name.to_lowercase(); + match res.insert(name_lower, U::Text(val)) { + None => (), + Some(U::Text(t)) => { + let name_lower = name.to_lowercase(); + let _ = res.insert(name_lower, U::List(vec![U::Text(t), U::Text(val)])); + () + }, + Some(U::List(mut l)) => { + let name_lower = name.to_lowercase(); + l.push(U::Text(val)); + let _ = res.insert(name_lower, U::List(l)); + () + }, + Some(o) => panic!("read-http: header not text nor list: {:?}", o), + } + } + res + } + + // tries to read until the end of the http header (deliniated by two newlines "\r\n\r\n") + fn read_till_end_of_header<R: Read>(buf: &mut Vec<u8>, reader: R) -> Option<()> { + let mut chonker = Chunkyboi::new(reader, 4096); + loop { + // TODO: attacker can send looooong input, set upper maximum + match chonker.next() { + Some(Ok(chunk)) => { + buf.extend_from_slice(&chunk); + if chunk.windows(4).any(|c| c == b"\r\n\r\n" ) { + return Some(()); + } + }, + Some(Err(err)) => die_temporary("read-http", format!("error reading from stdin: {:?}", err)), + None => return None + } + } + } + + // max header size chosen arbitrarily + let mut headers = [httparse::EMPTY_HEADER; 128]; + let stdin = std::io::stdin(); + + match what { + Request => { + let mut req = httparse::Request::new(&mut headers); + let mut buf: Vec<u8> = vec![]; + match read_till_end_of_header(&mut buf, stdin.lock()) { + Some(()) => match req.parse(&buf) { + Ok(httparse::Status::Complete(_body_start)) => {}, + Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"), + Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err)) + }, + None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse request headers")) + } + let method = req.method.expect("method must be filled on complete parse"); + let path = req.path.expect("path must be filled on complete parse"); + write_dict_req(method, path, &normalize_headers(req.headers)) + }, + Response => { + let mut resp = httparse::Response::new(&mut headers); + let mut buf: Vec<u8> = vec![]; + match read_till_end_of_header(&mut buf, stdin.lock()) { + Some(()) => match resp.parse(&buf) { + Ok(httparse::Status::Complete(_body_start)) => {}, + Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"), + Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err)) + }, + None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse response headers")) + } + let code = resp.code.expect("code must be filled on complete parse"); + let reason = resp.reason.expect("reason must be filled on complete parse"); + write_dict_resp(code, reason, &normalize_headers(resp.headers)) + } + } +} + +fn write_dict_req<'a, 'buf>(method: &'buf str, path: &'buf str, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> { + let mut http = vec![ + ("method", U::Text(method)), + ("path", U::Text(path)), + ].into_iter().collect(); + write_dict(http, headers) +} + +fn write_dict_resp<'a, 'buf>(code: u16, reason: &'buf str, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> { + let mut http = vec![ + ("status", U::N6(code as u64)), + ("status-text", U::Text(reason)), + ].into_iter().collect(); + write_dict(http, headers) +} + + +fn write_dict<'buf, 'a>(mut http: HashMap<&str, U<'a>>, headers: &'a HashMap<String, U<'a>>) -> std::io::Result<()> { + match http.insert("headers", U::Record( + headers.iter().map(|(k,v)| (k.as_str(), v.clone())).collect() + )) { + None => (), + Some(_) => panic!("read-http: headers already in dict"), + }; + netencode::encode( + &mut std::io::stdout(), + &U::Record(http) + )?; + Ok(()) +} + + +// iter helper + +struct Chunkyboi<T> { + inner: T, + buf: Vec<u8>, +} + +impl<R: Read> Chunkyboi<R> { + fn new(inner: R, chunksize: usize) -> Self { + let buf = vec![0; chunksize]; + Chunkyboi { + inner, + buf + } + } +} + +impl<R: Read> Iterator for Chunkyboi<R> { + type Item = std::io::Result<Vec<u8>>; + + fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> { + match self.inner.read(&mut self.buf) { + Ok(0) => None, + Ok(read) => { + // clone a new buffer so we can reuse the internal one + Some(Ok(self.buf[..read].to_owned())) + } + Err(err) => Some(Err(err)) + } + } +} diff --git a/users/Profpatsch/reverse-haskell-deps.hs b/users/Profpatsch/reverse-haskell-deps.hs new file mode 100644 index 000000000000..6b644df9ecc6 --- /dev/null +++ b/users/Profpatsch/reverse-haskell-deps.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE ScopedTypeVariables #-} +import qualified Text.HTML.TagSoup as Tag +import qualified Data.Text as Text +import Data.Text (Text) +import qualified Data.List as List +import Data.Maybe +import Text.Nicify +import qualified Text.Read as Read +import Numeric.Natural +import Data.Either +import qualified Data.ByteString as ByteString +import qualified Data.Text.Encoding + +parseNat :: Text.Text -> Maybe Natural +parseNat = Read.readMaybe . Text.unpack + +printNice :: Show a => a -> IO () +printNice = putStrLn . nicify . show + +type Tag = Tag.Tag Text.Text + +main = do + reverseHtml <- readStdinUtf8 + printNice $ List.sortOn snd $ packagesAndReverseDeps reverseHtml + + where + readStdinUtf8 = Data.Text.Encoding.decodeUtf8 <$> ByteString.getContents + +-- | reads the table provided by https://packdeps.haskellers.com/reverse +-- figuring out all sections (starting with the link to the package name), +-- then figuring out the name of the package and the first column, +-- which is the number of reverse dependencies of the package +packagesAndReverseDeps reverseHtml = do + let tags = Tag.parseTags reverseHtml + let sections = Tag.partitions (isJust . reverseLink) tags + let sectionNames = map (fromJust . reverseLink . head) sections + mapMaybe + (\(name :: Text.Text, sect) -> do + reverseDeps <- firstNaturalNumber sect + pure (sectionPackageName name sect, reverseDeps) :: Maybe (Text.Text, Natural)) + $ zip sectionNames sections + + + where + reverseLink = \case + Tag.TagOpen "a" attrs -> mapFind attrReverseLink attrs + _ -> Nothing + + attrReverseLink = \case + ("href", lnk) -> if + | "packdeps.haskellers.com/reverse/" `Text.isInfixOf` lnk -> Just lnk + | otherwise -> Nothing + _ -> Nothing + + sectionPackageName :: Text -> [Tag] -> Text + sectionPackageName sectionName = \case + (_: Tag.TagText name : _) -> name + (_: el : _) -> sectionName + xs -> sectionName + + + firstNaturalNumber :: [Tag] -> Maybe Natural + firstNaturalNumber = + mapFind (\case + Tag.TagText t -> parseNat t + _ -> Nothing) + + mapFind :: (a -> Maybe b) -> [a] -> Maybe b + mapFind f xs = fromJust . f <$> List.find (isJust . f) xs diff --git a/users/Profpatsch/reverse-haskell-deps.nix b/users/Profpatsch/reverse-haskell-deps.nix new file mode 100644 index 000000000000..b47347ea9fea --- /dev/null +++ b/users/Profpatsch/reverse-haskell-deps.nix @@ -0,0 +1,26 @@ +{ depot, pkgs, ... }: + +# Parses https://packdeps.haskellers.com/reverse +# and outputs the amount of reverse dependencies of each hackage package. + +let + + rev = depot.nix.writeExecline "reverse-haskell-deps" {} [ + "pipeline" [ + "${pkgs.curl}/bin/curl" "-L" "https://packdeps.haskellers.com/reverse" + ] + rev-hs + + ]; + + rev-hs = pkgs.writers.writeHaskell "revers-haskell-deps-hs" { + libraries = [ + pkgs.haskellPackages.nicify-lib + pkgs.haskellPackages.tagsoup + ]; + + } + ./reverse-haskell-deps.hs; + + +in rev diff --git a/users/Profpatsch/struct-edit/default.nix b/users/Profpatsch/struct-edit/default.nix new file mode 100644 index 000000000000..970cdd4d028b --- /dev/null +++ b/users/Profpatsch/struct-edit/default.nix @@ -0,0 +1,13 @@ +{ depot, ... }: +depot.nix.buildGo.program { + name = "struct-edit"; + srcs = [ + ./main.go + ]; + deps = [ + depot.third_party.gopkgs."github.com".charmbracelet.bubbletea + depot.third_party.gopkgs."github.com".charmbracelet.lipgloss + depot.third_party.gopkgs."github.com".muesli.termenv + depot.third_party.gopkgs."github.com".mattn.go-isatty + ]; +} diff --git a/users/Profpatsch/struct-edit/main.go b/users/Profpatsch/struct-edit/main.go new file mode 100644 index 000000000000..7e43074266d9 --- /dev/null +++ b/users/Profpatsch/struct-edit/main.go @@ -0,0 +1,431 @@ +package main + +import ( + json "encoding/json" + "fmt" + "log" + "os" + "strings" + "sort" + + tea "github.com/charmbracelet/bubbletea" + lipgloss "github.com/charmbracelet/lipgloss" + // termenv "github.com/muesli/termenv" + // isatty "github.com/mattn/go-isatty" +) + +// Keeps the full data structure and a path that indexes our current position into it. +type model struct { + path []index + data val +} + +// an index into a value, uint for lists and string for maps. +// nil for any scalar value. +// TODO: use an actual interface for these +type index interface{} + +/// recursive value that we can represent. +type val struct { + // the โtypeโ of value; see tag const belove + tag tag + // last known position of our cursor + last_index index + // documentation (TODO) + doc string + // the actual value; + // the actual structure is behind a pointer so we can replace the struct. + // determined by the tag + // tagString -> *string + // tagFloat -> *float64 + // tagList -> *[]val + // tagMap -> *map[string]val + val interface{} +} + +type tag string + +const ( + tagString tag = "string" + tagFloat tag = "float" + tagList tag = "list" + tagMap tag = "map" +) + +// print a value, flat +func (v val) Render() string { + s := "" + switch v.tag { + case tagString: + s += *v.val.(*string) + case tagFloat: + s += fmt.Sprint(*v.val.(*float64)) + case tagList: + s += "[ " + vs := []string{} + for _, enum := range v.enumerate() { + vs = append(vs, enum.v.Render()) + } + s += strings.Join(vs, ", ") + s += " ]" + case tagMap: + s += "{ " + vs := []string{} + for _, enum := range v.enumerate() { + vs = append(vs, fmt.Sprintf("%s: %s", enum.i.(string), enum.v.Render())) + } + s += strings.Join(vs, ", ") + s += " }" + default: + s += fmt.Sprintf("<unknown: %v>", v) + } + return s +} + +// render an index, depending on the type +func renderIndex(i index) (s string) { + switch i := i.(type) { + case nil: + s = "" + // list index + case uint: + s = "*" + // map index + case string: + s = i + ":" + } + return +} + +// take an arbitrary (within restrictions) go value and construct a val from it +func makeVal(i interface{}) val { + var v val + switch i := i.(type) { + case string: + v = val{ + tag: tagString, + last_index: index(nil), + doc: "", + val: &i, + } + case float64: + v = val{ + tag: tagFloat, + last_index: index(nil), + doc: "", + val: &i, + } + case []interface{}: + ls := []val{} + for _, i := range i { + ls = append(ls, makeVal(i)) + } + v = val{ + tag: tagList, + last_index: pos1Inner(tagList, &ls), + doc: "", + val: &ls, + } + case map[string]interface{}: + ls := map[string]val{} + for k, i := range i { + ls[k] = makeVal(i) + } + v = val{ + tag: tagMap, + last_index: pos1Inner(tagMap, &ls), + doc: "", + val: &ls, + } + default: + log.Fatalf("makeVal: cannot read json of type %T", i) + } + return v +} + +// return an index that points at the first entry in val +func (v val) pos1() index { + return v.enumerate()[0].i +} + +func pos1Inner(tag tag, v interface{}) index { + return enumerateInner(tag, v)[0].i +} + +type enumerate struct { + i index + v val +} + +// enumerate gives us a stable ordering of elements in this val. +// for scalars itโs just a nil index & the val itself. +// Guaranteed to always return at least one element. +func (v val) enumerate() (e []enumerate) { + e = enumerateInner(v.tag, v.val) + if e == nil { + e = append(e, enumerate{ + i: nil, + v: v, + }) + } + return +} + +// like enumerate, but returns an empty slice for scalars without inner vals. +func enumerateInner(tag tag, v interface{}) (e []enumerate) { + switch tag { + case tagString: + fallthrough + case tagFloat: + e = nil + case tagList: + for i, v := range *v.(*[]val) { + e = append(e, enumerate{i: index(uint(i)), v: v}) + } + case tagMap: + // map sorting order is not stable (actually randomized thank jabber) + // so letโs sort them + keys := []string{} + m := *v.(*map[string]val) + for k, _ := range m { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + e = append(e, enumerate{i: index(k), v: m[k]}) + } + default: + log.Fatalf("unknown val tag %s, %v", tag, v) + } + return +} + +func (m model) PathString() string { + s := "/ " + var is []string + for _, v := range m.path { + is = append(is, fmt.Sprintf("%v", v)) + } + s += strings.Join(is, " / ") + return s +} + +// walk the given path down in data, to get the value at that point. +// Assumes that all path indexes are valid indexes into data. +// Returns a pointer to the value at point, in order to be able to change it. +func walk(data *val, path []index) (*val, bool, error) { + res := data + atPath := func(index int) string { + return fmt.Sprintf("at path %v", path[:index+1]) + } + errf := func(ty string, val interface{}, index int) error { + return fmt.Errorf("walk: canโt walk into %s %v %s", ty, val, atPath(index)) + } + for i, p := range path { + switch res.tag { + case tagString: + return nil, true, nil + case tagFloat: + return nil, true, nil + case tagList: + switch p := p.(type) { + case uint: + list := *res.val.(*[]val) + if int(p) >= len(list) || p < 0 { + return nil, false, fmt.Errorf("index out of bounds %s", atPath(i)) + } + res = &list[p] + default: + return nil, false, fmt.Errorf("not a list index %s", atPath(i)) + } + case tagMap: + switch p := p.(type) { + case string: + m := *res.val.(*map[string]val) + if a, ok := m[p]; ok { + res = &a + } else { + return nil, false, fmt.Errorf("index %s not in map %s", p, atPath(i)) + } + default: + return nil, false, fmt.Errorf("not a map index %v %s", p, atPath(i)) + } + + default: + return nil, false, errf(string(res.tag), res.val, i) + } + } + return res, false, nil +} + +// descend into the selected index. Assumes that the index is valid. +// Will not descend into scalars. +func (m model) descend() (model, error) { + // TODO: two walks?! + this, _, err := walk(&m.data, m.path) + if err != nil { + return m, err + } + newPath := append(m.path, this.last_index) + _, bounce, err := walk(&m.data, newPath) + if err != nil { + return m, err + } + // only descend if we *can* + if !bounce { + m.path = newPath + } + return m, nil +} + +// ascend to one level up. stops at the root. +func (m model) ascend() (model, error) { + if len(m.path) > 0 { + m.path = m.path[:len(m.path)-1] + _, _, err := walk(&m.data, m.path) + return m, err + } + return m, nil +} + +/// go to the next item, or wraparound +func (min model) next() (m model, err error) { + m = min + this, _, err := walk(&m.data, m.path) + if err != nil { + return + } + enumL := this.enumerate() + setNext := false + for _, enum := range enumL { + if setNext { + this.last_index = enum.i + setNext = false + break + } + if enum.i == this.last_index { + setNext = true + } + } + // wraparound + if setNext { + this.last_index = enumL[0].i + } + return +} + +/// go to the previous item, or wraparound +func (min model) prev() (m model, err error) { + m = min + this, _, err := walk(&m.data, m.path) + if err != nil { + return + } + enumL := this.enumerate() + // last element, wraparound + prevIndex := enumL[len(enumL)-1].i + for _, enum := range enumL { + if enum.i == this.last_index { + this.last_index = prevIndex + break + } + prevIndex = enum.i + } + return +} + +/// bubbletea implementations + +func (m model) Init() tea.Cmd { + return nil +} + +func initialModel(v interface{}) model { + val := makeVal(v) + return model{ + path: []index{}, + data: val, + } +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var err error + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + + case "up": + m, err = m.prev() + + case "down": + m, err = m.next() + + case "right": + m, err = m.descend() + + case "left": + m, err = m.ascend() + + // case "enter": + // _, ok := m.selected[m.cursor] + // if ok { + // delete(m.selected, m.cursor) + // } else { + // m.selected[m.cursor] = struct{}{} + // } + } + + } + if err != nil { + log.Fatal(err) + } + return m, nil +} + +var pathColor = lipgloss.NewStyle(). + // light blue + Foreground(lipgloss.Color("12")) + +var selectedColor = lipgloss.NewStyle(). + Bold(true) + +func (m model) View() string { + s := pathColor.Render(m.PathString()) + cur, _, err := walk(&m.data, m.path) + if err != nil { + log.Fatal(err) + } + s += cur.doc + "\n" + s += "\n" + for _, enum := range cur.enumerate() { + is := renderIndex(enum.i) + if is != "" { + s += is + " " + } + if enum.i == cur.last_index { + s += selectedColor.Render(enum.v.Render()) + } else { + s += enum.v.Render() + } + s += "\n" + } + + // s += fmt.Sprintf("%v\n", m) + // s += fmt.Sprintf("%v\n", cur) + + return s +} + +func main() { + var input interface{} + err := json.NewDecoder(os.Stdin).Decode(&input) + if err != nil { + log.Fatal("json from stdin: ", err) + } + p := tea.NewProgram(initialModel(input)) + if err := p.Start(); err != nil { + log.Fatal("bubbletea TUI error: ", err) + } +} diff --git a/users/Profpatsch/tree-sitter.nix b/users/Profpatsch/tree-sitter.nix new file mode 100644 index 000000000000..4f81b8e7a77c --- /dev/null +++ b/users/Profpatsch/tree-sitter.nix @@ -0,0 +1,179 @@ +{ depot, pkgs, lib, ... }: + +let + bins = depot.nix.getBins pkgs.coreutils [ "head" "printf" "cat" ] + // depot.nix.getBins pkgs.ncurses [ "tput" ] + // depot.nix.getBins pkgs.bc [ "bc" ] + // depot.nix.getBins pkgs.ocamlPackages.sexp [ "sexp" ]; + + print-ast = depot.nix.writers.rustSimple { + name = "print-ast"; + dependencies = with depot.third_party.rust-crates; [ + libloading + tree-sitter + ]; + } '' + extern crate libloading; + extern crate tree_sitter; + use std::mem; + use std::io::{Read}; + use libloading::{Library, Symbol}; + use tree_sitter::{Language, Parser}; + + /// Load the shared lib FILE and return the language under SYMBOL-NAME. + /// Inspired by the rust source of emacs-tree-sitter. + fn _load_language(file: String, symbol_name: String) -> Result<Language, libloading::Error> { + let lib = Library::new(file)?; + let tree_sitter_lang: Symbol<'_, unsafe extern "C" fn() -> _> = + unsafe { lib.get(symbol_name.as_bytes())? }; + let language: Language = unsafe { tree_sitter_lang() }; + // Avoid segmentation fault by not unloading the lib, as language is a static piece of data. + // TODO: Attach an Rc<Library> to Language instead. + mem::forget(lib); + Ok(language) + } + + fn main() { + let mut args = std::env::args(); + let so = args.nth(1).unwrap(); + let symbol_name = args.nth(0).unwrap(); + let file = args.nth(0).unwrap(); + let mut parser = Parser::new(); + let lang = _load_language(so, symbol_name).unwrap(); + parser.set_language(lang).unwrap(); + let bytes = std::fs::read(&file).unwrap(); + print!("{}", parser.parse(&bytes, None).unwrap().root_node().to_sexp()); + } + + + ''; + + tree-sitter-nix = buildTreeSitterGrammar { + language = "tree-sitter-nix"; + source = pkgs.fetchFromGitHub { + owner = "cstrahan"; + repo = "tree-sitter-nix"; + rev = "791b5ff0e4f0da358cbb941788b78d436a2ca621"; + sha256 = "1y5b3wh3fcmbgq8r2i97likzfp1zp02m58zacw5a1cjqs5raqz66"; + }; + }; + + watch-file-modified = depot.nix.writers.rustSimple { + name = "watch-file-modified"; + dependencies = [ + depot.third_party.rust-crates.inotify + depot.users.Profpatsch.netstring.rust-netstring + ]; + } '' + extern crate inotify; + extern crate netstring; + use inotify::{EventMask, WatchMask, Inotify}; + use std::io::Write; + + fn main() { + let mut inotify = Inotify::init() + .expect("Failed to initialize inotify"); + + let file = std::env::args().nth(1).unwrap(); + + let file_watch = inotify + .add_watch( + &file, + WatchMask::MODIFY + ) + .expect("Failed to add inotify watch"); + + let mut buffer = [0u8; 4096]; + loop { + let events = inotify + .read_events_blocking(&mut buffer) + .expect("Failed to read inotify events"); + + for event in events { + if event.wd == file_watch { + std::io::stdout().write(&netstring::to_netstring(file.as_bytes())); + std::io::stdout().flush(); + } + } + } + } + + ''; + + # clear screen and set LINES and COLUMNS to terminal height & width + clear-screen = depot.nix.writeExecline "clear-screen" {} [ + "if" [ bins.tput "clear" ] + "backtick" "-in" "LINES" [ bins.tput "lines" ] + "backtick" "-in" "COLUMNS" [ bins.tput "cols" ] + "$@" + ]; + + print-nix-file = depot.nix.writeExecline "print-nix-file" { readNArgs = 1; } [ + "pipeline" [ print-ast "${tree-sitter-nix}/parser" "tree_sitter_nix" "$1" ] + "pipeline" [ bins.sexp "print" ] + clear-screen + "importas" "-ui" "lines" "LINES" + "backtick" "-in" "ls" [ + "pipeline" + # when you pull out bc to decrement an integer itโs time to switch to python lol + [ bins.printf "x=%s; --x\n" "$lines" ] + bins.bc + ] + "importas" "-ui" "l" "ls" + bins.head "-n\${l}" + ]; + + print-nix-file-on-update = depot.nix.writeExecline "print-nix-file-on-update" { readNArgs = 1; } [ + "if" [ print-nix-file "$1" ] + "pipeline" [ watch-file-modified "$1" ] + "forstdin" "-d" "" "file" + "importas" "file" "file" + print-nix-file "$file" + ]; + + # copied from nixpkgs + buildTreeSitterGrammar = + { + # language name + language + # source for the language grammar + , source + }: + + pkgs.stdenv.mkDerivation { + + pname = "${language}-grammar"; + inherit (pkgs.tree-sitter) version; + + src = source; + + buildInputs = [ pkgs.tree-sitter ]; + + dontUnpack = true; + configurePhase= ":"; + buildPhase = '' + runHook preBuild + scanner_cc="$src/src/scanner.cc" + if [ ! -f "$scanner_cc" ]; then + scanner_cc="" + fi + $CXX -I$src/src/ -c $scanner_cc + $CC -I$src/src/ -shared -o parser -Os scanner.o $src/src/parser.c -lstdc++ + runHook postBuild + ''; + installPhase = '' + runHook preInstall + mkdir $out + mv parser $out/ + runHook postInstall + ''; + }; + +in depot.nix.readTree.drvTargets { + inherit + print-ast + tree-sitter-nix + print-nix-file-on-update + watch-file-modified + ; +} diff --git a/users/Profpatsch/writers/default.nix b/users/Profpatsch/writers/default.nix new file mode 100644 index 000000000000..3151a9d3bd44 --- /dev/null +++ b/users/Profpatsch/writers/default.nix @@ -0,0 +1,76 @@ +{ depot, pkgs, lib, ... }: +let + bins = depot.nix.getBins pkgs.s6-portable-utils ["s6-mkdir" "s6-cat" "s6-ln" "s6-ls" "s6-touch" ] + // depot.nix.getBins pkgs.coreutils ["printf" ]; + + inherit (depot.nix.yants) defun struct restrict attrs list string drv any; + + inherit (depot.nix) drvSeqL; + + FlakeError = + restrict + "flake error" + (s: lib.any (prefix: (builtins.substring 0 1 s) == prefix) + [ "E" "W" ]) + string; + Libraries = defun [ (attrs any) (list drv) ]; + + python3 = { + name, + libraries ? (_: []), + flakeIgnore ? [] + }: pkgs.writers.writePython3 name { + libraries = Libraries libraries pkgs.python3Packages; + flakeIgnore = + let ignoreTheseErrors = [ + # whitespace after { + "E201" + # whitespace before } + "E202" + # fuck 4-space indentation + "E121" "E111" + # who cares about blank lines โฆ + # โฆ at end of files + "W391" + # โฆ between functions + "E302" "E305" + ]; + in list FlakeError (ignoreTheseErrors ++ flakeIgnore); + }; + + # TODO: add the same flake check as the pyhon3 writer + python3Lib = { name, libraries ? (_: []) }: moduleString: + let srcTree = depot.nix.runExecline.local name { stdin = moduleString; } [ + "importas" "out" "out" + "if" [ bins.s6-mkdir "-p" "\${out}/${name}" ] + "if" [ + "redirfd" "-w" "1" "\${out}/setup.py" + bins.printf '' + from distutils.core import setup + + setup( + name='%s', + packages=['%s'] + ) + '' name name + ] + "if" [ + # redirect stdin to the init py + "redirfd" "-w" "1" "\${out}/${name}/__init__.py" + bins.s6-cat + ] + ]; + in pkgs.python3Packages.buildPythonPackage { + inherit name; + src = srcTree; + propagatedBuildInputs = libraries pkgs.python3Packages; + doCheck = false; + }; + + +in { + inherit + python3 + python3Lib + ; +} diff --git a/users/Profpatsch/writers/tests/default.nix b/users/Profpatsch/writers/tests/default.nix new file mode 100644 index 000000000000..dc760af9e16e --- /dev/null +++ b/users/Profpatsch/writers/tests/default.nix @@ -0,0 +1,48 @@ +{ depot, pkgs, ... }: + +let + inherit (depot.users.Profpatsch.writers) + python3Lib + python3 + ; + + inherit (pkgs) + coreutils + ; + + run = drv: depot.nix.runExecline.local "run-${drv.name}" {} [ + "if" [ drv ] + "importas" "out" "out" + "${coreutils}/bin/touch" "$out" + ]; + + pythonTransitiveLib = python3Lib { + name = "transitive"; + } '' + def transitive(s): + return s + " 1 2 3" + ''; + + pythonTestLib = python3Lib { + name = "test_lib"; + libraries = _: [ pythonTransitiveLib ]; + } '' + import transitive + def test(): + return transitive.transitive("test") + ''; + + pythonWithLib = run (python3 { + name = "python-with-lib"; + libraries = _: [ pythonTestLib ]; + } '' + import test_lib + + assert(test_lib.test() == "test 1 2 3") + ''); + +in depot.nix.readTree.drvTargets { + inherit + pythonWithLib + ; +} diff --git a/users/cynthia/OWNERS b/users/cynthia/OWNERS new file mode 100644 index 000000000000..da62f3777af0 --- /dev/null +++ b/users/cynthia/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - cynthia diff --git a/users/cynthia/keys.nix b/users/cynthia/keys.nix new file mode 100644 index 000000000000..bac8dc1c57ae --- /dev/null +++ b/users/cynthia/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + all = [ + "cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICsj3W6QczgxE3s5GGT8qg0aLrCM+QeRnSq9RkiZtKvz meow" + ]; +} \ No newline at end of file diff --git a/users/edef/OWNERS b/users/edef/OWNERS new file mode 100644 index 000000000000..05f7639c8947 --- /dev/null +++ b/users/edef/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - edef diff --git a/users/edef/depot-scan/default.nix b/users/edef/depot-scan/default.nix new file mode 100644 index 000000000000..a9c0f382ff45 --- /dev/null +++ b/users/edef/depot-scan/default.nix @@ -0,0 +1,12 @@ +{ pkgs, ... }: + +pkgs.writeShellScriptBin "depot-scan" '' + set -euo pipefail + + path="''${1:-$(git rev-parse --show-prefix)}" + path="''${path%%/}" + attr="''${path//\//.}" + root="$(git rev-parse --show-toplevel)" + echo "scanning //$path" >&2 + nix-instantiate -E "import ${./wrap.nix} $root" -A "$attr" -vv 2> >(${pkgs.perl}/bin/perl ${./depot-scan.pl}) >&2 +'' diff --git a/users/edef/depot-scan/depot-scan.pl b/users/edef/depot-scan/depot-scan.pl new file mode 100755 index 000000000000..8808e2eb0023 --- /dev/null +++ b/users/edef/depot-scan/depot-scan.pl @@ -0,0 +1,11 @@ +#! /usr/bin/env -S perl -ln +use strict; + +if (/^evaluating file '(.*)'$/ or + /^copied source '(.*)' -> '.*'$/ or + /^trace: depot-scan '(.*)'$/) { + print $1; + next; +} + +print STDERR unless /^instantiated '.*' -> '.*'$/; diff --git a/users/edef/depot-scan/wrap.nix b/users/edef/depot-scan/wrap.nix new file mode 100644 index 000000000000..dcb557a24b1e --- /dev/null +++ b/users/edef/depot-scan/wrap.nix @@ -0,0 +1,15 @@ +# this wraps import to override readFile and readDir to trace the files it touches +# technique inspired by lorri +let + + global = { + import = global.scopedImport {}; + scopedImport = x: builtins.scopedImport (global // x); + builtins = builtins // { + inherit (global) import scopedImport; + readFile = path: builtins.trace "depot-scan '${toString path}'" (builtins.readFile path); + readDir = path: builtins.trace "depot-scan '${toString path}'" (builtins.readDir path); + }; + }; + +in global.import diff --git a/users/edef/keys.nix b/users/edef/keys.nix new file mode 100644 index 000000000000..53e88c9e7345 --- /dev/null +++ b/users/edef/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + all = [ + "cert-authority ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCvb/7ojfcbKvHIyjnrNUOOgzy44tCkgXY9HLuyFta1jQOE9pFIK19B4dR9bOglPKf145CCL0mSFJNNqmNwwavU2uRn+TQrW+U1dQAk8Gt+gh3O49YE854hwwyMU+xD6bIuUdfxPr+r5al/Ov5Km28ZMlHOs3FoAP0hInK+eAibioxL5rVJOtgicrOVCkGoXEgnuG+LRbOYTwzdClhRUxiPjK8alCbcJQ53AeZHO4G6w9wTr+W5ILCfvW4OmUXCX01sKzaBiQuuFCF6M/H4LlnsPWLMra2twXxkOIhZblwC+lncps9lQaUgiD4koZeOCORvHW00G0L39ilFbbnVcL6Itp/m8RRWm/xRxS4RMnsdV/AhvpRLrhL3lfQ7E2oCeSM36v1S9rdg6a47zcnpL+ahG76Gz39Y7KmVRQciNx7ezbwxj3Q5lZtFykgdfGIAN+bT8ijXMO6m68g60i9Bz4IoMZGkiJGqMYLTxMQ+oRgR3Ro5lbj7E11YBHyeimoBYXYGHMkiuxopQZ7lIj3plxIzhmUlXJBA4jMw9KGHdYaLhaicIYhvQmCTAjrkt2HvxEe6lU8iws2Qv+pB6tAGundN36RVVWAckeQPZ4ZsgDP8V2FfibZ1nsrQ+zBKqaslYMAHs01Cf0Hm0PnCqagf230xaobu0iooNuXx44QKoDnB+w== openpgp:0x803010E7" + ]; +} diff --git a/users/ericvolp12/OWNERS b/users/ericvolp12/OWNERS new file mode 100644 index 000000000000..5a012a695bf3 --- /dev/null +++ b/users/ericvolp12/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - ericvolp12 diff --git a/users/eta/OWNERS b/users/eta/OWNERS new file mode 100644 index 000000000000..f212e89e2ae9 --- /dev/null +++ b/users/eta/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - eta diff --git a/users/eta/keys.nix b/users/eta/keys.nix new file mode 100644 index 000000000000..247a182843f1 --- /dev/null +++ b/users/eta/keys.nix @@ -0,0 +1,12 @@ +{ ... }: + +let + keys = { + yubikey4 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD6E1wuWaXQARNoLnmlOJndwI7/ms3Ga7MJxsUvFtaSiy3g8h/hz4WgyR7YT+hUYjFihh/YkGS9Zy9aEqAa5zBGLcZtgj1O0qOl2joynm679zdlcwAart74fXSJYYupT9tFeXXeWLO1g054lVJ5xZ9KLpBBk+6yzlmmm5KuoitKBqBbadzsqAeKhNn1Nq9ITPU4vxTFk+sXp/nxk/JoUOM8S2N4YuoX9OVenDHKh9DtOcvDZhlosGmunO33/YaU2XB95ZE6cNhEtVlkbyR3a2SsAYz1qGgfH0HSyoK3LJoAM4Aiz99ktuKiI/zMy4k4TV00OCi1sCPEjzUoijZRZt5FMH/TVr9dJROVjHcL9g9//fW3jwqojf7uuJFlTJb47RxjTk4Jb4F6K7HhOs7bgh3WuOjvhyRYbCYcg+RfnwjJk+hfM5GcjZ8J4UZdNc5LyIcfH8W1v9DADBCgz7QcmfrfMloYtEgjK/5XVrtBtiMtUOgpfKujawF55d1Vj26+CxeID8NHMXzZYEMeyRpi/WXlC+lq1Wx4Fj8gvideOw/3gAdj2G3SJWdSPk8XpIFQ1fm3tXB0ltyV5TszIJhfMnmsKJeEm3YlTCR1sMW7nr3wEdMqa6mpcWZTWU+dppmAGr2c+OGSnXkCi7Z2h/YJE6X+izrOrqRspG2fCM8GlfRFWw== cardno:000607469311"; + yubikey5 = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKCJx23px0Vknw1NlD+arcqeVXxcogPUMJgF/PGp6wA/tg7hHUKs2udC+gDMYlxQ9IpnWOwZ/9yvqzTDwUU3R/4= YubiKey #15026444 PIV Slot 9a"; + }; + configs = { + whitby = [ keys.yubikey4 keys.yubikey5 ]; + }; +in + configs diff --git a/users/firefly/OWNERS b/users/firefly/OWNERS new file mode 100644 index 000000000000..55d62a5723a5 --- /dev/null +++ b/users/firefly/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - firefly diff --git a/users/firefly/keys.nix b/users/firefly/keys.nix new file mode 100644 index 000000000000..1d7467a0747c --- /dev/null +++ b/users/firefly/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +rec { + as = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN9i8fs10/BjNEqFXD+3fQeQ0SuHnQx4WpuqUg4caeed firefly@as"; + + whitby = [ as ]; +} diff --git a/users/flokli/OWNERS b/users/flokli/OWNERS new file mode 100644 index 000000000000..63e0fbda3c7f --- /dev/null +++ b/users/flokli/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - flokli diff --git a/users/flokli/keys.nix b/users/flokli/keys.nix new file mode 100644 index 000000000000..790c9862f824 --- /dev/null +++ b/users/flokli/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + all = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli" + ]; +} diff --git a/users/grfn/OWNERS b/users/grfn/OWNERS new file mode 100644 index 000000000000..da7ac5cb9ef9 --- /dev/null +++ b/users/grfn/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - grfn diff --git a/users/grfn/achilles/.envrc b/users/grfn/achilles/.envrc new file mode 100644 index 000000000000..051d09d292a8 --- /dev/null +++ b/users/grfn/achilles/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/users/grfn/achilles/.gitignore b/users/grfn/achilles/.gitignore new file mode 100644 index 000000000000..ea8c4bf7f35f --- /dev/null +++ b/users/grfn/achilles/.gitignore @@ -0,0 +1 @@ +/target diff --git a/users/grfn/achilles/Cargo.lock b/users/grfn/achilles/Cargo.lock new file mode 100644 index 000000000000..30e5e021f147 --- /dev/null +++ b/users/grfn/achilles/Cargo.lock @@ -0,0 +1,868 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "achilles" +version = "0.1.0" +dependencies = [ + "anyhow", + "bimap", + "clap", + "crate-root", + "derive_more", + "inkwell", + "itertools", + "lazy_static", + "llvm-sys", + "nom", + "nom-trace", + "pratt", + "pretty_assertions", + "proptest", + "test-strategy", + "thiserror", + "void", +] + +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92b72b8f03128773278bf74418b9205f3d2a12c39a61f92395f47af390c32bf" + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crate-root" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6fe4622b269032d2c5140a592d67a9c409031d286174fcde172fbed86f0d3" + +[[package]] +name = "ctor" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inkwell" +version = "0.1.0" +source = "git+https://github.com/TheDan64/inkwell?branch=master#a2db15b0bd1c06d71763585ae10d9ea4e775da0c" +dependencies = [ + "either", + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "parking_lot", + "regex", +] + +[[package]] +name = "inkwell_internals" +version = "0.3.0" +source = "git+https://github.com/TheDan64/inkwell?branch=master#a2db15b0bd1c06d71763585ae10d9ea4e775da0c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" + +[[package]] +name = "llvm-sys" +version = "110.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ede189444b8c78907e5d36da5dabcf153170fcff9c1dba48afc4b33c7e19f0" +dependencies = [ + "cc", + "lazy_static", + "libc", + "regex", + "semver", +] + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "nom-trace" +version = "0.2.1" +source = "git+https://github.com/glittershark/nom-trace?branch=nom-6#6168d2e15cc51efd12d80260159b76a764dba138" +dependencies = [ + "nom", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "os_str_bytes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" + +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "pratt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bbc12f7936a7b195790dd6d9b982b66c54f45ff6766decf25c44cac302dce" + +[[package]] +name = "pretty_assertions" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.0", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-strategy" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2328963c69243416e811c88066d18f670792b2e36e17fa57f4b1a124f85d18a8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/users/grfn/achilles/Cargo.toml b/users/grfn/achilles/Cargo.toml new file mode 100644 index 000000000000..f091399a0dd4 --- /dev/null +++ b/users/grfn/achilles/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "achilles" +version = "0.1.0" +authors = ["Griffin Smith <root@gws.fyi>"] +edition = "2018" + +[dependencies] +anyhow = "1.0.38" +bimap = "0.6.0" +clap = "3.0.0-beta.2" +derive_more = "0.99.11" +inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm11-0"] } +itertools = "0.10.0" +lazy_static = "1.4.0" +llvm-sys = "110.0.1" +nom = "6.1.2" +nom-trace = { git = "https://github.com/glittershark/nom-trace", branch = "nom-6" } +pratt = "0.3.0" +proptest = "1.0.0" +test-strategy = "0.1.1" +thiserror = "1.0.24" +void = "1.0.2" + +[dev-dependencies] +crate-root = "0.1.3" +pretty_assertions = "0.7.1" diff --git a/users/grfn/achilles/ach/.gitignore b/users/grfn/achilles/ach/.gitignore new file mode 100644 index 000000000000..ac5296ebbd74 --- /dev/null +++ b/users/grfn/achilles/ach/.gitignore @@ -0,0 +1,7 @@ +*.ll +*.o + +functions +simple +externs +units diff --git a/users/grfn/achilles/ach/Makefile b/users/grfn/achilles/ach/Makefile new file mode 100644 index 000000000000..3a8cd2865e87 --- /dev/null +++ b/users/grfn/achilles/ach/Makefile @@ -0,0 +1,15 @@ +default: simple + +%.ll: %.ach + cargo run -- compile $< -o $@ -f llvm + +%.o: %.ll + llc $< -o $@ -filetype=obj + +%: %.o + clang $< -o $@ + +.PHONY: clean + +clean: + @rm -f *.ll *.o simple functions diff --git a/users/grfn/achilles/ach/externs.ach b/users/grfn/achilles/ach/externs.ach new file mode 100644 index 000000000000..faf8ce90e353 --- /dev/null +++ b/users/grfn/achilles/ach/externs.ach @@ -0,0 +1,5 @@ +extern puts : fn cstring -> int + +fn main = + let _ = puts "foobar" + in 0 diff --git a/users/grfn/achilles/ach/functions.ach b/users/grfn/achilles/ach/functions.ach new file mode 100644 index 000000000000..dc6e7a1f3e34 --- /dev/null +++ b/users/grfn/achilles/ach/functions.ach @@ -0,0 +1,8 @@ +ty id : fn a -> a +fn id x = x + +ty plus : fn int -> int +fn plus (x: int) (y: int) = x + y + +ty main : fn -> int +fn main = plus (id 2) 7 diff --git a/users/grfn/achilles/ach/simple.ach b/users/grfn/achilles/ach/simple.ach new file mode 100644 index 000000000000..20f1677235c0 --- /dev/null +++ b/users/grfn/achilles/ach/simple.ach @@ -0,0 +1 @@ +fn main = let x = 2; y = 3 in x + y diff --git a/users/grfn/achilles/ach/units.ach b/users/grfn/achilles/ach/units.ach new file mode 100644 index 000000000000..70635d978c7c --- /dev/null +++ b/users/grfn/achilles/ach/units.ach @@ -0,0 +1,7 @@ +extern puts : fn cstring -> int + +ty print : fn cstring -> () +fn print x = let _ = puts x in () + +ty main : fn -> int +fn main = let _ = print "hi" in 0 diff --git a/users/grfn/achilles/default.nix b/users/grfn/achilles/default.nix new file mode 100644 index 000000000000..5245049d4a32 --- /dev/null +++ b/users/grfn/achilles/default.nix @@ -0,0 +1,24 @@ +{ depot, pkgs, ... }: + +let + llvmPackages = pkgs.llvmPackages_11; +in + +depot.third_party.naersk.buildPackage { + src = ./.; + + buildInputs = [ + llvmPackages.clang + llvmPackages.llvm + llvmPackages.bintools + llvmPackages.libclang.lib + ] ++ (with pkgs; [ + zlib + ncurses + libxml2 + libffi + pkgconfig + ]); + + doCheck = true; +} diff --git a/users/grfn/achilles/shell.nix b/users/grfn/achilles/shell.nix new file mode 100644 index 000000000000..f32dce3ba39d --- /dev/null +++ b/users/grfn/achilles/shell.nix @@ -0,0 +1,18 @@ +with (import ../../.. {}).third_party.nixpkgs; + +mkShell { + buildInputs = [ + clang_11 + llvm_11.lib + llvmPackages_11.bintools + llvmPackages_11.clang + llvmPackages_11.libclang.lib + zlib + ncurses + libxml2 + libffi + pkg-config + ]; + + LLVM_SYS_110_PREFIX = llvmPackages_11.bintools; +} diff --git a/users/grfn/achilles/src/ast/hir.rs b/users/grfn/achilles/src/ast/hir.rs new file mode 100644 index 000000000000..cdfaef567d7a --- /dev/null +++ b/users/grfn/achilles/src/ast/hir.rs @@ -0,0 +1,364 @@ +use std::collections::HashMap; + +use itertools::Itertools; + +use super::{BinaryOperator, Ident, Literal, UnaryOperator}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Pattern<'a, T> { + Id(Ident<'a>, T), + Tuple(Vec<Pattern<'a, T>>), +} + +impl<'a, T> Pattern<'a, T> { + pub fn to_owned(&self) -> Pattern<'static, T> + where + T: Clone, + { + match self { + Pattern::Id(id, t) => Pattern::Id(id.to_owned(), t.clone()), + Pattern::Tuple(pats) => { + Pattern::Tuple(pats.into_iter().map(Pattern::to_owned).collect()) + } + } + } + + pub fn traverse_type<F, U, E>(self, f: F) -> Result<Pattern<'a, U>, E> + where + F: Fn(T) -> Result<U, E> + Clone, + { + match self { + Pattern::Id(id, t) => Ok(Pattern::Id(id, f(t)?)), + Pattern::Tuple(pats) => Ok(Pattern::Tuple( + pats.into_iter() + .map(|pat| pat.traverse_type(f.clone())) + .collect::<Result<Vec<_>, _>>()?, + )), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Binding<'a, T> { + pub pat: Pattern<'a, T>, + pub body: Expr<'a, T>, +} + +impl<'a, T> Binding<'a, T> { + fn to_owned(&self) -> Binding<'static, T> + where + T: Clone, + { + Binding { + pat: self.pat.to_owned(), + body: self.body.to_owned(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Expr<'a, T> { + Ident(Ident<'a>, T), + + Literal(Literal<'a>, T), + + Tuple(Vec<Expr<'a, T>>, T), + + UnaryOp { + op: UnaryOperator, + rhs: Box<Expr<'a, T>>, + type_: T, + }, + + BinaryOp { + lhs: Box<Expr<'a, T>>, + op: BinaryOperator, + rhs: Box<Expr<'a, T>>, + type_: T, + }, + + Let { + bindings: Vec<Binding<'a, T>>, + body: Box<Expr<'a, T>>, + type_: T, + }, + + If { + condition: Box<Expr<'a, T>>, + then: Box<Expr<'a, T>>, + else_: Box<Expr<'a, T>>, + type_: T, + }, + + Fun { + type_args: Vec<Ident<'a>>, + args: Vec<(Ident<'a>, T)>, + body: Box<Expr<'a, T>>, + type_: T, + }, + + Call { + fun: Box<Expr<'a, T>>, + type_args: HashMap<Ident<'a>, T>, + args: Vec<Expr<'a, T>>, + type_: T, + }, +} + +impl<'a, T> Expr<'a, T> { + pub fn type_(&self) -> &T { + match self { + Expr::Ident(_, t) => t, + Expr::Literal(_, t) => t, + Expr::Tuple(_, t) => t, + Expr::UnaryOp { type_, .. } => type_, + Expr::BinaryOp { type_, .. } => type_, + Expr::Let { type_, .. } => type_, + Expr::If { type_, .. } => type_, + Expr::Fun { type_, .. } => type_, + Expr::Call { type_, .. } => type_, + } + } + + pub fn traverse_type<F, U, E>(self, f: F) -> Result<Expr<'a, U>, E> + where + F: Fn(T) -> Result<U, E> + Clone, + { + match self { + Expr::Ident(id, t) => Ok(Expr::Ident(id, f(t)?)), + Expr::Literal(lit, t) => Ok(Expr::Literal(lit, f(t)?)), + Expr::UnaryOp { op, rhs, type_ } => Ok(Expr::UnaryOp { + op, + rhs: Box::new(rhs.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Expr::BinaryOp { + lhs, + op, + rhs, + type_, + } => Ok(Expr::BinaryOp { + lhs: Box::new(lhs.traverse_type(f.clone())?), + op, + rhs: Box::new(rhs.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Expr::Let { + bindings, + body, + type_, + } => Ok(Expr::Let { + bindings: bindings + .into_iter() + .map(|Binding { pat, body }| { + Ok(Binding { + pat: pat.traverse_type(f.clone())?, + body: body.traverse_type(f.clone())?, + }) + }) + .collect::<Result<Vec<_>, E>>()?, + body: Box::new(body.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Expr::If { + condition, + then, + else_, + type_, + } => Ok(Expr::If { + condition: Box::new(condition.traverse_type(f.clone())?), + then: Box::new(then.traverse_type(f.clone())?), + else_: Box::new(else_.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Expr::Fun { + args, + type_args, + body, + type_, + } => Ok(Expr::Fun { + args: args + .into_iter() + .map(|(id, t)| Ok((id, f.clone()(t)?))) + .collect::<Result<Vec<_>, E>>()?, + type_args, + body: Box::new(body.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Expr::Call { + fun, + type_args, + args, + type_, + } => Ok(Expr::Call { + fun: Box::new(fun.traverse_type(f.clone())?), + type_args: type_args + .into_iter() + .map(|(id, ty)| Ok((id, f.clone()(ty)?))) + .collect::<Result<HashMap<_, _>, E>>()?, + args: args + .into_iter() + .map(|e| e.traverse_type(f.clone())) + .collect::<Result<Vec<_>, E>>()?, + type_: f(type_)?, + }), + Expr::Tuple(members, t) => Ok(Expr::Tuple( + members + .into_iter() + .map(|t| t.traverse_type(f.clone())) + .try_collect()?, + f(t)?, + )), + } + } + + pub fn to_owned(&self) -> Expr<'static, T> + where + T: Clone, + { + match self { + Expr::Ident(id, t) => Expr::Ident(id.to_owned(), t.clone()), + Expr::Literal(lit, t) => Expr::Literal(lit.to_owned(), t.clone()), + Expr::UnaryOp { op, rhs, type_ } => Expr::UnaryOp { + op: *op, + rhs: Box::new((**rhs).to_owned()), + type_: type_.clone(), + }, + Expr::BinaryOp { + lhs, + op, + rhs, + type_, + } => Expr::BinaryOp { + lhs: Box::new((**lhs).to_owned()), + op: *op, + rhs: Box::new((**rhs).to_owned()), + type_: type_.clone(), + }, + Expr::Let { + bindings, + body, + type_, + } => Expr::Let { + bindings: bindings.iter().map(|b| b.to_owned()).collect(), + body: Box::new((**body).to_owned()), + type_: type_.clone(), + }, + Expr::If { + condition, + then, + else_, + type_, + } => Expr::If { + condition: Box::new((**condition).to_owned()), + then: Box::new((**then).to_owned()), + else_: Box::new((**else_).to_owned()), + type_: type_.clone(), + }, + Expr::Fun { + args, + type_args, + body, + type_, + } => Expr::Fun { + args: args + .iter() + .map(|(id, t)| (id.to_owned(), t.clone())) + .collect(), + type_args: type_args.iter().map(|arg| arg.to_owned()).collect(), + body: Box::new((**body).to_owned()), + type_: type_.clone(), + }, + Expr::Call { + fun, + type_args, + args, + type_, + } => Expr::Call { + fun: Box::new((**fun).to_owned()), + type_args: type_args + .iter() + .map(|(id, t)| (id.to_owned(), t.clone())) + .collect(), + args: args.iter().map(|e| e.to_owned()).collect(), + type_: type_.clone(), + }, + Expr::Tuple(members, t) => { + Expr::Tuple(members.into_iter().map(Expr::to_owned).collect(), t.clone()) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Decl<'a, T> { + Fun { + name: Ident<'a>, + type_args: Vec<Ident<'a>>, + args: Vec<(Ident<'a>, T)>, + body: Box<Expr<'a, T>>, + type_: T, + }, + + Extern { + name: Ident<'a>, + arg_types: Vec<T>, + ret_type: T, + }, +} + +impl<'a, T> Decl<'a, T> { + pub fn name(&self) -> &Ident<'a> { + match self { + Decl::Fun { name, .. } => name, + Decl::Extern { name, .. } => name, + } + } + + pub fn set_name(&mut self, new_name: Ident<'a>) { + match self { + Decl::Fun { name, .. } => *name = new_name, + Decl::Extern { name, .. } => *name = new_name, + } + } + + pub fn type_(&self) -> Option<&T> { + match self { + Decl::Fun { type_, .. } => Some(type_), + Decl::Extern { .. } => None, + } + } + + pub fn traverse_type<F, U, E>(self, f: F) -> Result<Decl<'a, U>, E> + where + F: Fn(T) -> Result<U, E> + Clone, + { + match self { + Decl::Fun { + name, + type_args, + args, + body, + type_, + } => Ok(Decl::Fun { + name, + type_args, + args: args + .into_iter() + .map(|(id, t)| Ok((id, f(t)?))) + .try_collect()?, + body: Box::new(body.traverse_type(f.clone())?), + type_: f(type_)?, + }), + Decl::Extern { + name, + arg_types, + ret_type, + } => Ok(Decl::Extern { + name, + arg_types: arg_types.into_iter().map(f.clone()).try_collect()?, + ret_type: f(ret_type)?, + }), + } + } +} diff --git a/users/grfn/achilles/src/ast/mod.rs b/users/grfn/achilles/src/ast/mod.rs new file mode 100644 index 000000000000..5438d29d2cf7 --- /dev/null +++ b/users/grfn/achilles/src/ast/mod.rs @@ -0,0 +1,484 @@ +pub(crate) mod hir; + +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt::{self, Display, Formatter}; + +use itertools::Itertools; + +#[derive(Debug, PartialEq, Eq)] +pub struct InvalidIdentifier<'a>(Cow<'a, str>); + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Ident<'a>(pub Cow<'a, str>); + +impl<'a> From<&'a Ident<'a>> for &'a str { + fn from(id: &'a Ident<'a>) -> Self { + id.0.as_ref() + } +} + +impl<'a> Display for Ident<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl<'a> Ident<'a> { + pub fn to_owned(&self) -> Ident<'static> { + Ident(Cow::Owned(self.0.clone().into_owned())) + } + + /// Construct an identifier from a &str without checking that it's a valid identifier + pub fn from_str_unchecked(s: &'a str) -> Self { + debug_assert!(is_valid_identifier(s)); + Self(Cow::Borrowed(s)) + } + + pub fn from_string_unchecked(s: String) -> Self { + debug_assert!(is_valid_identifier(&s)); + Self(Cow::Owned(s)) + } +} + +pub fn is_valid_identifier<S>(s: &S) -> bool +where + S: AsRef<str> + ?Sized, +{ + s.as_ref() + .chars() + .any(|c| !c.is_alphanumeric() || !"_".contains(c)) +} + +impl<'a> TryFrom<&'a str> for Ident<'a> { + type Error = InvalidIdentifier<'a>; + + fn try_from(s: &'a str) -> Result<Self, Self::Error> { + if is_valid_identifier(s) { + Ok(Ident(Cow::Borrowed(s))) + } else { + Err(InvalidIdentifier(Cow::Borrowed(s))) + } + } +} + +impl<'a> TryFrom<String> for Ident<'a> { + type Error = InvalidIdentifier<'static>; + + fn try_from(s: String) -> Result<Self, Self::Error> { + if is_valid_identifier(&s) { + Ok(Ident(Cow::Owned(s))) + } else { + Err(InvalidIdentifier(Cow::Owned(s))) + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum BinaryOperator { + /// `+` + Add, + + /// `-` + Sub, + + /// `*` + Mul, + + /// `/` + Div, + + /// `^` + Pow, + + /// `==` + Equ, + + /// `!=` + Neq, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum UnaryOperator { + /// ! + Not, + + /// - + Neg, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Literal<'a> { + Unit, + Int(u64), + Bool(bool), + String(Cow<'a, str>), +} + +impl<'a> Literal<'a> { + pub fn to_owned(&self) -> Literal<'static> { + match self { + Literal::Int(i) => Literal::Int(*i), + Literal::Bool(b) => Literal::Bool(*b), + Literal::String(s) => Literal::String(Cow::Owned(s.clone().into_owned())), + Literal::Unit => Literal::Unit, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Pattern<'a> { + Id(Ident<'a>), + Tuple(Vec<Pattern<'a>>), +} + +impl<'a> Pattern<'a> { + pub fn to_owned(&self) -> Pattern<'static> { + match self { + Pattern::Id(id) => Pattern::Id(id.to_owned()), + Pattern::Tuple(pats) => Pattern::Tuple(pats.iter().map(Pattern::to_owned).collect()), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Binding<'a> { + pub pat: Pattern<'a>, + pub type_: Option<Type<'a>>, + pub body: Expr<'a>, +} + +impl<'a> Binding<'a> { + fn to_owned(&self) -> Binding<'static> { + Binding { + pat: self.pat.to_owned(), + type_: self.type_.as_ref().map(|t| t.to_owned()), + body: self.body.to_owned(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Expr<'a> { + Ident(Ident<'a>), + + Literal(Literal<'a>), + + UnaryOp { + op: UnaryOperator, + rhs: Box<Expr<'a>>, + }, + + BinaryOp { + lhs: Box<Expr<'a>>, + op: BinaryOperator, + rhs: Box<Expr<'a>>, + }, + + Let { + bindings: Vec<Binding<'a>>, + body: Box<Expr<'a>>, + }, + + If { + condition: Box<Expr<'a>>, + then: Box<Expr<'a>>, + else_: Box<Expr<'a>>, + }, + + Fun(Box<Fun<'a>>), + + Call { + fun: Box<Expr<'a>>, + args: Vec<Expr<'a>>, + }, + + Tuple(Vec<Expr<'a>>), + + Ascription { + expr: Box<Expr<'a>>, + type_: Type<'a>, + }, +} + +impl<'a> Expr<'a> { + pub fn to_owned(&self) -> Expr<'static> { + match self { + Expr::Ident(ref id) => Expr::Ident(id.to_owned()), + Expr::Literal(ref lit) => Expr::Literal(lit.to_owned()), + Expr::Tuple(ref members) => { + Expr::Tuple(members.into_iter().map(Expr::to_owned).collect()) + } + Expr::UnaryOp { op, rhs } => Expr::UnaryOp { + op: *op, + rhs: Box::new((**rhs).to_owned()), + }, + Expr::BinaryOp { lhs, op, rhs } => Expr::BinaryOp { + lhs: Box::new((**lhs).to_owned()), + op: *op, + rhs: Box::new((**rhs).to_owned()), + }, + Expr::Let { bindings, body } => Expr::Let { + bindings: bindings.iter().map(|binding| binding.to_owned()).collect(), + body: Box::new((**body).to_owned()), + }, + Expr::If { + condition, + then, + else_, + } => Expr::If { + condition: Box::new((**condition).to_owned()), + then: Box::new((**then).to_owned()), + else_: Box::new((**else_).to_owned()), + }, + Expr::Fun(fun) => Expr::Fun(Box::new((**fun).to_owned())), + Expr::Call { fun, args } => Expr::Call { + fun: Box::new((**fun).to_owned()), + args: args.iter().map(|arg| arg.to_owned()).collect(), + }, + Expr::Ascription { expr, type_ } => Expr::Ascription { + expr: Box::new((**expr).to_owned()), + type_: type_.to_owned(), + }, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Arg<'a> { + pub ident: Ident<'a>, + pub type_: Option<Type<'a>>, +} + +impl<'a> Arg<'a> { + pub fn to_owned(&self) -> Arg<'static> { + Arg { + ident: self.ident.to_owned(), + type_: self.type_.as_ref().map(Type::to_owned), + } + } +} + +impl<'a> TryFrom<&'a str> for Arg<'a> { + type Error = <Ident<'a> as TryFrom<&'a str>>::Error; + + fn try_from(value: &'a str) -> Result<Self, Self::Error> { + Ok(Arg { + ident: Ident::try_from(value)?, + type_: None, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Fun<'a> { + pub args: Vec<Arg<'a>>, + pub body: Expr<'a>, +} + +impl<'a> Fun<'a> { + pub fn to_owned(&self) -> Fun<'static> { + Fun { + args: self.args.iter().map(|arg| arg.to_owned()).collect(), + body: self.body.to_owned(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Decl<'a> { + Fun { + name: Ident<'a>, + body: Fun<'a>, + }, + Ascription { + name: Ident<'a>, + type_: Type<'a>, + }, + Extern { + name: Ident<'a>, + type_: FunctionType<'a>, + }, +} + +//// + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct FunctionType<'a> { + pub args: Vec<Type<'a>>, + pub ret: Box<Type<'a>>, +} + +impl<'a> FunctionType<'a> { + pub fn to_owned(&self) -> FunctionType<'static> { + FunctionType { + args: self.args.iter().map(|a| a.to_owned()).collect(), + ret: Box::new((*self.ret).to_owned()), + } + } +} + +impl<'a> Display for FunctionType<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "fn {} -> {}", self.args.iter().join(", "), self.ret) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Type<'a> { + Int, + Float, + Bool, + CString, + Unit, + Tuple(Vec<Type<'a>>), + Var(Ident<'a>), + Function(FunctionType<'a>), +} + +impl<'a> Type<'a> { + pub fn to_owned(&self) -> Type<'static> { + match self { + Type::Int => Type::Int, + Type::Float => Type::Float, + Type::Bool => Type::Bool, + Type::CString => Type::CString, + Type::Unit => Type::Unit, + Type::Var(v) => Type::Var(v.to_owned()), + Type::Function(f) => Type::Function(f.to_owned()), + Type::Tuple(members) => Type::Tuple(members.iter().map(Type::to_owned).collect()), + } + } + + pub fn alpha_equiv(&self, other: &Self) -> bool { + fn do_alpha_equiv<'a>( + substs: &mut HashMap<&'a Ident<'a>, &'a Ident<'a>>, + lhs: &'a Type, + rhs: &'a Type, + ) -> bool { + match (lhs, rhs) { + (Type::Var(v1), Type::Var(v2)) => substs.entry(v1).or_insert(v2) == &v2, + ( + Type::Function(FunctionType { + args: args1, + ret: ret1, + }), + Type::Function(FunctionType { + args: args2, + ret: ret2, + }), + ) => { + args1.len() == args2.len() + && args1 + .iter() + .zip(args2) + .all(|(a1, a2)| do_alpha_equiv(substs, a1, a2)) + && do_alpha_equiv(substs, ret1, ret2) + } + _ => lhs == rhs, + } + } + + let mut substs = HashMap::new(); + do_alpha_equiv(&mut substs, self, other) + } + + pub fn traverse_type_vars<'b, F>(self, mut f: F) -> Type<'b> + where + F: FnMut(Ident<'a>) -> Type<'b> + Clone, + { + match self { + Type::Var(tv) => f(tv), + Type::Function(FunctionType { args, ret }) => Type::Function(FunctionType { + args: args + .into_iter() + .map(|t| t.traverse_type_vars(f.clone())) + .collect(), + ret: Box::new(ret.traverse_type_vars(f)), + }), + Type::Int => Type::Int, + Type::Float => Type::Float, + Type::Bool => Type::Bool, + Type::CString => Type::CString, + Type::Tuple(members) => Type::Tuple( + members + .into_iter() + .map(|t| t.traverse_type_vars(f.clone())) + .collect(), + ), + Type::Unit => Type::Unit, + } + } + + pub fn as_tuple(&self) -> Option<&Vec<Type<'a>>> { + if let Self::Tuple(v) = self { + Some(v) + } else { + None + } + } +} + +impl<'a> Display for Type<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Int => f.write_str("int"), + Type::Float => f.write_str("float"), + Type::Bool => f.write_str("bool"), + Type::CString => f.write_str("cstring"), + Type::Unit => f.write_str("()"), + Type::Var(v) => v.fmt(f), + Type::Function(ft) => ft.fmt(f), + Type::Tuple(ms) => write!(f, "({})", ms.iter().join(", ")), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn type_var(n: &str) -> Type<'static> { + Type::Var(Ident::try_from(n.to_owned()).unwrap()) + } + + mod alpha_equiv { + use super::*; + + #[test] + fn trivial() { + assert!(Type::Int.alpha_equiv(&Type::Int)); + assert!(!Type::Int.alpha_equiv(&Type::Bool)); + } + + #[test] + fn simple_type_var() { + assert!(type_var("a").alpha_equiv(&type_var("b"))); + } + + #[test] + fn function_with_type_vars_equiv() { + assert!(Type::Function(FunctionType { + args: vec![type_var("a")], + ret: Box::new(type_var("b")), + }) + .alpha_equiv(&Type::Function(FunctionType { + args: vec![type_var("b")], + ret: Box::new(type_var("a")), + }))) + } + + #[test] + fn function_with_type_vars_non_equiv() { + assert!(!Type::Function(FunctionType { + args: vec![type_var("a")], + ret: Box::new(type_var("a")), + }) + .alpha_equiv(&Type::Function(FunctionType { + args: vec![type_var("b")], + ret: Box::new(type_var("a")), + }))) + } + } +} diff --git a/users/grfn/achilles/src/codegen/llvm.rs b/users/grfn/achilles/src/codegen/llvm.rs new file mode 100644 index 000000000000..9a71ac954e00 --- /dev/null +++ b/users/grfn/achilles/src/codegen/llvm.rs @@ -0,0 +1,486 @@ +use std::convert::{TryFrom, TryInto}; +use std::path::Path; +use std::result; + +use inkwell::basic_block::BasicBlock; +use inkwell::builder::Builder; +pub use inkwell::context::Context; +use inkwell::module::Module; +use inkwell::support::LLVMString; +use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, IntType, StructType}; +use inkwell::values::{AnyValueEnum, BasicValueEnum, FunctionValue, StructValue}; +use inkwell::{AddressSpace, IntPredicate}; +use itertools::Itertools; +use thiserror::Error; + +use crate::ast::hir::{Binding, Decl, Expr, Pattern}; +use crate::ast::{BinaryOperator, Ident, Literal, Type, UnaryOperator}; +use crate::common::env::Env; + +#[derive(Debug, PartialEq, Eq, Error)] +pub enum Error { + #[error("Undefined variable {0}")] + UndefinedVariable(Ident<'static>), + + #[error("LLVM Error: {0}")] + LLVMError(String), +} + +impl From<LLVMString> for Error { + fn from(s: LLVMString) -> Self { + Self::LLVMError(s.to_string()) + } +} + +pub type Result<T> = result::Result<T, Error>; + +pub struct Codegen<'ctx, 'ast> { + context: &'ctx Context, + pub module: Module<'ctx>, + builder: Builder<'ctx>, + env: Env<&'ast Ident<'ast>, AnyValueEnum<'ctx>>, + function_stack: Vec<FunctionValue<'ctx>>, + identifier_counter: u32, +} + +impl<'ctx, 'ast> Codegen<'ctx, 'ast> { + pub fn new(context: &'ctx Context, module_name: &str) -> Self { + let module = context.create_module(module_name); + let builder = context.create_builder(); + Self { + context, + module, + builder, + env: Default::default(), + function_stack: Default::default(), + identifier_counter: 0, + } + } + + pub fn new_function<'a>( + &'a mut self, + name: &str, + ty: FunctionType<'ctx>, + ) -> &'a FunctionValue<'ctx> { + self.function_stack + .push(self.module.add_function(name, ty, None)); + let basic_block = self.append_basic_block("entry"); + self.builder.position_at_end(basic_block); + self.function_stack.last().unwrap() + } + + pub fn finish_function(&mut self, res: Option<&BasicValueEnum<'ctx>>) -> FunctionValue<'ctx> { + self.builder.build_return(match res { + // lol + Some(val) => Some(val), + None => None, + }); + self.function_stack.pop().unwrap() + } + + pub fn append_basic_block(&self, name: &str) -> BasicBlock<'ctx> { + self.context + .append_basic_block(*self.function_stack.last().unwrap(), name) + } + + fn bind_pattern(&mut self, pat: &'ast Pattern<'ast, Type>, val: AnyValueEnum<'ctx>) { + match pat { + Pattern::Id(id, _) => self.env.set(id, val), + Pattern::Tuple(pats) => { + for (i, pat) in pats.iter().enumerate() { + let member = self + .builder + .build_extract_value( + StructValue::try_from(val).unwrap(), + i as _, + "pat_bind", + ) + .unwrap(); + self.bind_pattern(pat, member.into()); + } + } + } + } + + pub fn codegen_expr( + &mut self, + expr: &'ast Expr<'ast, Type>, + ) -> Result<Option<AnyValueEnum<'ctx>>> { + match expr { + Expr::Ident(id, _) => self + .env + .resolve(id) + .cloned() + .ok_or_else(|| Error::UndefinedVariable(id.to_owned())) + .map(Some), + Expr::Literal(lit, ty) => { + let ty = self.codegen_int_type(ty); + match lit { + Literal::Int(i) => Ok(Some(AnyValueEnum::IntValue(ty.const_int(*i, false)))), + Literal::Bool(b) => Ok(Some(AnyValueEnum::IntValue( + ty.const_int(if *b { 1 } else { 0 }, false), + ))), + Literal::String(s) => Ok(Some( + self.builder + .build_global_string_ptr(s, "s") + .as_pointer_value() + .into(), + )), + Literal::Unit => Ok(None), + } + } + Expr::UnaryOp { op, rhs, .. } => { + let rhs = self.codegen_expr(rhs)?.unwrap(); + match op { + UnaryOperator::Not => unimplemented!(), + UnaryOperator::Neg => Ok(Some(AnyValueEnum::IntValue( + self.builder.build_int_neg(rhs.into_int_value(), "neg"), + ))), + } + } + Expr::BinaryOp { lhs, op, rhs, .. } => { + let lhs = self.codegen_expr(lhs)?.unwrap(); + let rhs = self.codegen_expr(rhs)?.unwrap(); + match op { + BinaryOperator::Add => { + Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_add( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + )))) + } + BinaryOperator::Sub => { + Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_sub( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + )))) + } + BinaryOperator::Mul => { + Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_sub( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + )))) + } + BinaryOperator::Div => Ok(Some(AnyValueEnum::IntValue( + self.builder.build_int_signed_div( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + ), + ))), + BinaryOperator::Pow => unimplemented!(), + BinaryOperator::Equ => Ok(Some(AnyValueEnum::IntValue( + self.builder.build_int_compare( + IntPredicate::EQ, + lhs.into_int_value(), + rhs.into_int_value(), + "eq", + ), + ))), + BinaryOperator::Neq => todo!(), + } + } + Expr::Let { bindings, body, .. } => { + self.env.push(); + for Binding { pat, body, .. } in bindings { + if let Some(val) = self.codegen_expr(body)? { + self.bind_pattern(pat, val); + } + } + let res = self.codegen_expr(body); + self.env.pop(); + res + } + Expr::If { + condition, + then, + else_, + type_, + } => { + let then_block = self.append_basic_block("then"); + let else_block = self.append_basic_block("else"); + let join_block = self.append_basic_block("join"); + let condition = self.codegen_expr(condition)?.unwrap(); + self.builder.build_conditional_branch( + condition.into_int_value(), + then_block, + else_block, + ); + self.builder.position_at_end(then_block); + let then_res = self.codegen_expr(then)?; + self.builder.build_unconditional_branch(join_block); + + self.builder.position_at_end(else_block); + let else_res = self.codegen_expr(else_)?; + self.builder.build_unconditional_branch(join_block); + + self.builder.position_at_end(join_block); + if let Some(phi_type) = self.codegen_type(type_) { + let phi = self.builder.build_phi(phi_type, "join"); + phi.add_incoming(&[ + ( + &BasicValueEnum::try_from(then_res.unwrap()).unwrap(), + then_block, + ), + ( + &BasicValueEnum::try_from(else_res.unwrap()).unwrap(), + else_block, + ), + ]); + Ok(Some(phi.as_basic_value().into())) + } else { + Ok(None) + } + } + Expr::Call { fun, args, .. } => { + if let Expr::Ident(id, _) = &**fun { + let function = self + .module + .get_function(id.into()) + .or_else(|| self.env.resolve(id)?.clone().try_into().ok()) + .ok_or_else(|| Error::UndefinedVariable(id.to_owned()))?; + let args = args + .iter() + .map(|arg| Ok(self.codegen_expr(arg)?.unwrap().try_into().unwrap())) + .collect::<Result<Vec<_>>>()?; + Ok(self + .builder + .build_call(function, &args, "call") + .try_as_basic_value() + .left() + .map(|val| val.into())) + } else { + todo!() + } + } + Expr::Fun { args, body, .. } => { + let fname = self.fresh_ident("f"); + let cur_block = self.builder.get_insert_block().unwrap(); + let env = self.env.save(); // TODO: closures + let function = self.codegen_function(&fname, args, body)?; + self.builder.position_at_end(cur_block); + self.env.restore(env); + Ok(Some(function.into())) + } + Expr::Tuple(members, ty) => { + let values = members + .into_iter() + .map(|expr| self.codegen_expr(expr)) + .collect::<Result<Vec<_>>>()? + .into_iter() + .filter_map(|x| x) + .map(|x| x.try_into().unwrap()) + .collect_vec(); + let field_types = ty.as_tuple().unwrap(); + let tuple_type = self.codegen_tuple_type(field_types); + Ok(Some(tuple_type.const_named_struct(&values).into())) + } + } + } + + pub fn codegen_function( + &mut self, + name: &str, + args: &'ast [(Ident<'ast>, Type)], + body: &'ast Expr<'ast, Type>, + ) -> Result<FunctionValue<'ctx>> { + let arg_types = args + .iter() + .filter_map(|(_, at)| self.codegen_type(at)) + .collect::<Vec<_>>(); + + self.new_function( + name, + match self.codegen_type(body.type_()) { + Some(ret_ty) => ret_ty.fn_type(&arg_types, false), + None => self.context.void_type().fn_type(&arg_types, false), + }, + ); + self.env.push(); + for (i, (arg, _)) in args.iter().enumerate() { + self.env.set( + arg, + self.cur_function().get_nth_param(i as u32).unwrap().into(), + ); + } + let res = self.codegen_expr(body)?; + self.env.pop(); + Ok(self.finish_function(res.map(|av| av.try_into().unwrap()).as_ref())) + } + + pub fn codegen_extern( + &mut self, + name: &str, + args: &'ast [Type], + ret: &'ast Type, + ) -> Result<()> { + let arg_types = args + .iter() + .map(|t| self.codegen_type(t).unwrap()) + .collect::<Vec<_>>(); + self.module.add_function( + name, + match self.codegen_type(ret) { + Some(ret_ty) => ret_ty.fn_type(&arg_types, false), + None => self.context.void_type().fn_type(&arg_types, false), + }, + None, + ); + Ok(()) + } + + pub fn codegen_decl(&mut self, decl: &'ast Decl<'ast, Type>) -> Result<()> { + match decl { + Decl::Fun { + name, args, body, .. + } => { + self.codegen_function(name.into(), args, body)?; + Ok(()) + } + Decl::Extern { + name, + arg_types, + ret_type, + } => self.codegen_extern(name.into(), arg_types, ret_type), + } + } + + pub fn codegen_main(&mut self, expr: &'ast Expr<'ast, Type>) -> Result<()> { + self.new_function("main", self.context.i64_type().fn_type(&[], false)); + let res = self.codegen_expr(expr)?; + if *expr.type_() != Type::Int { + self.builder + .build_return(Some(&self.context.i64_type().const_int(0, false))); + } else { + self.finish_function(res.map(|r| r.try_into().unwrap()).as_ref()); + } + Ok(()) + } + + fn codegen_type(&self, type_: &'ast Type) -> Option<BasicTypeEnum<'ctx>> { + // TODO + match type_ { + Type::Int => Some(self.context.i64_type().into()), + Type::Float => Some(self.context.f64_type().into()), + Type::Bool => Some(self.context.bool_type().into()), + Type::CString => Some( + self.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .into(), + ), + Type::Function(_) => todo!(), + Type::Var(_) => unreachable!(), + Type::Unit => None, + Type::Tuple(ts) => Some(self.codegen_tuple_type(ts).into()), + } + } + + fn codegen_tuple_type(&self, ts: &'ast [Type]) -> StructType<'ctx> { + self.context.struct_type( + ts.iter() + .filter_map(|t| self.codegen_type(t)) + .collect_vec() + .as_slice(), + false, + ) + } + + fn codegen_int_type(&self, type_: &'ast Type) -> IntType<'ctx> { + // TODO + self.context.i64_type() + } + + pub fn print_to_file<P>(&self, path: P) -> Result<()> + where + P: AsRef<Path>, + { + Ok(self.module.print_to_file(path)?) + } + + pub fn binary_to_file<P>(&self, path: P) -> Result<()> + where + P: AsRef<Path>, + { + if self.module.write_bitcode_to_path(path.as_ref()) { + Ok(()) + } else { + Err(Error::LLVMError( + "Error writing bitcode to output path".to_owned(), + )) + } + } + + fn fresh_ident(&mut self, prefix: &str) -> String { + self.identifier_counter += 1; + format!("{}{}", prefix, self.identifier_counter) + } + + fn cur_function(&self) -> &FunctionValue<'ctx> { + self.function_stack.last().unwrap() + } +} + +#[cfg(test)] +mod tests { + use inkwell::execution_engine::JitFunction; + use inkwell::OptimizationLevel; + + use super::*; + + fn jit_eval<T>(expr: &str) -> anyhow::Result<T> { + let expr = crate::parser::expr(expr).unwrap().1; + + let expr = crate::tc::typecheck_expr(expr).unwrap(); + + let context = Context::create(); + let mut codegen = Codegen::new(&context, "test"); + let execution_engine = codegen + .module + .create_jit_execution_engine(OptimizationLevel::None) + .unwrap(); + + codegen.codegen_function("test", &[], &expr)?; + + unsafe { + let fun: JitFunction<unsafe extern "C" fn() -> T> = + execution_engine.get_function("test")?; + Ok(fun.call()) + } + } + + #[test] + fn add_literals() { + assert_eq!(jit_eval::<i64>("1 + 2").unwrap(), 3); + } + + #[test] + fn variable_shadowing() { + assert_eq!( + jit_eval::<i64>("let x = 1 in (let x = 2 in x) + x").unwrap(), + 3 + ); + } + + #[test] + fn eq() { + assert_eq!( + jit_eval::<i64>("let x = 1 in if x == 1 then 2 else 4").unwrap(), + 2 + ); + } + + #[test] + fn function_call() { + let res = jit_eval::<i64>("let id = fn x = x in id 1").unwrap(); + assert_eq!(res, 1); + } + + #[test] + fn bind_tuple_pattern() { + let res = jit_eval::<i64>("let (x, y) = (1, 2) in x + y").unwrap(); + assert_eq!(res, 3); + } +} diff --git a/users/grfn/achilles/src/codegen/mod.rs b/users/grfn/achilles/src/codegen/mod.rs new file mode 100644 index 000000000000..8ef057dba04f --- /dev/null +++ b/users/grfn/achilles/src/codegen/mod.rs @@ -0,0 +1,25 @@ +pub mod llvm; + +use inkwell::execution_engine::JitFunction; +use inkwell::OptimizationLevel; +pub use llvm::*; + +use crate::ast::hir::Expr; +use crate::ast::Type; +use crate::common::Result; + +pub fn jit_eval<T>(expr: &Expr<Type>) -> Result<T> { + let context = Context::create(); + let mut codegen = Codegen::new(&context, "eval"); + let execution_engine = codegen + .module + .create_jit_execution_engine(OptimizationLevel::None) + .map_err(Error::from)?; + codegen.codegen_function("test", &[], &expr)?; + + unsafe { + let fun: JitFunction<unsafe extern "C" fn() -> T> = + execution_engine.get_function("eval").unwrap(); + Ok(fun.call()) + } +} diff --git a/users/grfn/achilles/src/commands/check.rs b/users/grfn/achilles/src/commands/check.rs new file mode 100644 index 000000000000..0bea482c1478 --- /dev/null +++ b/users/grfn/achilles/src/commands/check.rs @@ -0,0 +1,39 @@ +use clap::Clap; +use std::path::PathBuf; + +use crate::ast::Type; +use crate::{parser, tc, Result}; + +/// Typecheck a file or expression +#[derive(Clap)] +pub struct Check { + /// File to check + path: Option<PathBuf>, + + /// Expression to check + #[clap(long, short = 'e')] + expr: Option<String>, +} + +fn run_expr(expr: String) -> Result<Type<'static>> { + let (_, parsed) = parser::expr(&expr)?; + let hir_expr = tc::typecheck_expr(parsed)?; + Ok(hir_expr.type_().to_owned()) +} + +fn run_path(path: PathBuf) -> Result<Type<'static>> { + todo!() +} + +impl Check { + pub fn run(self) -> Result<()> { + let type_ = match (self.path, self.expr) { + (None, None) => Err("Must specify either a file or expression to check".into()), + (Some(_), Some(_)) => Err("Cannot specify both a file and expression to check".into()), + (None, Some(expr)) => run_expr(expr), + (Some(path), None) => run_path(path), + }?; + println!("type: {}", type_); + Ok(()) + } +} diff --git a/users/grfn/achilles/src/commands/compile.rs b/users/grfn/achilles/src/commands/compile.rs new file mode 100644 index 000000000000..be8767575ab5 --- /dev/null +++ b/users/grfn/achilles/src/commands/compile.rs @@ -0,0 +1,31 @@ +use std::path::PathBuf; + +use clap::Clap; + +use crate::common::Result; +use crate::compiler::{self, CompilerOptions}; + +/// Compile a source file +#[derive(Clap)] +pub struct Compile { + /// File to compile + file: PathBuf, + + /// Output file + #[clap(short = 'o')] + out_file: PathBuf, + + #[clap(flatten)] + options: CompilerOptions, +} + +impl Compile { + pub fn run(self) -> Result<()> { + eprintln!( + ">>> {} -> {}", + &self.file.to_string_lossy(), + self.out_file.to_string_lossy() + ); + compiler::compile_file(&self.file, &self.out_file, &self.options) + } +} diff --git a/users/grfn/achilles/src/commands/eval.rs b/users/grfn/achilles/src/commands/eval.rs new file mode 100644 index 000000000000..61a712c08a8e --- /dev/null +++ b/users/grfn/achilles/src/commands/eval.rs @@ -0,0 +1,32 @@ +use clap::Clap; + +use crate::codegen; +use crate::interpreter; +use crate::parser; +use crate::tc; +use crate::Result; + +/// Evaluate an expression and print its result +#[derive(Clap)] +pub struct Eval { + /// JIT-compile with LLVM instead of interpreting + #[clap(long)] + jit: bool, + + /// Expression to evaluate + expr: String, +} + +impl Eval { + pub fn run(self) -> Result<()> { + let (_, parsed) = parser::expr(&self.expr)?; + let hir = tc::typecheck_expr(parsed)?; + let result = if self.jit { + codegen::jit_eval::<i64>(&hir)?.into() + } else { + interpreter::eval(&hir)? + }; + println!("{}", result); + Ok(()) + } +} diff --git a/users/grfn/achilles/src/commands/mod.rs b/users/grfn/achilles/src/commands/mod.rs new file mode 100644 index 000000000000..fd0a822708c2 --- /dev/null +++ b/users/grfn/achilles/src/commands/mod.rs @@ -0,0 +1,7 @@ +pub mod check; +pub mod compile; +pub mod eval; + +pub use check::Check; +pub use compile::Compile; +pub use eval::Eval; diff --git a/users/grfn/achilles/src/common/env.rs b/users/grfn/achilles/src/common/env.rs new file mode 100644 index 000000000000..59a5e46c466f --- /dev/null +++ b/users/grfn/achilles/src/common/env.rs @@ -0,0 +1,59 @@ +use std::borrow::Borrow; +use std::collections::HashMap; +use std::hash::Hash; +use std::mem; + +/// A lexical environment +#[derive(Debug, PartialEq, Eq)] +pub struct Env<K: Eq + Hash, V>(Vec<HashMap<K, V>>); + +impl<K, V> Default for Env<K, V> +where + K: Eq + Hash, +{ + fn default() -> Self { + Self::new() + } +} + +impl<K, V> Env<K, V> +where + K: Eq + Hash, +{ + pub fn new() -> Self { + Self(vec![Default::default()]) + } + + pub fn push(&mut self) { + self.0.push(Default::default()); + } + + pub fn pop(&mut self) { + self.0.pop(); + } + + pub fn save(&mut self) -> Self { + mem::take(self) + } + + pub fn restore(&mut self, saved: Self) { + *self = saved; + } + + pub fn set(&mut self, k: K, v: V) { + self.0.last_mut().unwrap().insert(k, v); + } + + pub fn resolve<'a, Q>(&'a self, k: &Q) -> Option<&'a V> + where + K: Borrow<Q>, + Q: Hash + Eq + ?Sized, + { + for ctx in self.0.iter().rev() { + if let Some(res) = ctx.get(k) { + return Some(res); + } + } + None + } +} diff --git a/users/grfn/achilles/src/common/error.rs b/users/grfn/achilles/src/common/error.rs new file mode 100644 index 000000000000..51575a895e91 --- /dev/null +++ b/users/grfn/achilles/src/common/error.rs @@ -0,0 +1,59 @@ +use std::{io, result}; + +use thiserror::Error; + +use crate::{codegen, interpreter, parser, tc}; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + IOError(#[from] io::Error), + + #[error("Error parsing input: {0}")] + ParseError(#[from] parser::Error), + + #[error("Error evaluating expression: {0}")] + EvalError(#[from] interpreter::Error), + + #[error("Compile error: {0}")] + CodegenError(#[from] codegen::Error), + + #[error("Type error: {0}")] + TypeError(#[from] tc::Error), + + #[error("{0}")] + Message(String), +} + +impl From<String> for Error { + fn from(s: String) -> Self { + Self::Message(s) + } +} + +impl<'a> From<&'a str> for Error { + fn from(s: &'a str) -> Self { + Self::Message(s.to_owned()) + } +} + +impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for Error { + fn from(e: nom::Err<nom::error::Error<&'a str>>) -> Self { + use nom::error::Error as NomError; + use nom::Err::*; + + Self::ParseError(match e { + Incomplete(i) => Incomplete(i), + Error(NomError { input, code }) => Error(NomError { + input: input.to_owned(), + code, + }), + Failure(NomError { input, code }) => Failure(NomError { + input: input.to_owned(), + code, + }), + }) + } +} + +pub type Result<T> = result::Result<T, Error>; diff --git a/users/grfn/achilles/src/common/mod.rs b/users/grfn/achilles/src/common/mod.rs new file mode 100644 index 000000000000..8368a6dd180f --- /dev/null +++ b/users/grfn/achilles/src/common/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod env; +pub(crate) mod error; +pub(crate) mod namer; + +pub use error::{Error, Result}; +pub use namer::{Namer, NamerOf}; diff --git a/users/grfn/achilles/src/common/namer.rs b/users/grfn/achilles/src/common/namer.rs new file mode 100644 index 000000000000..016e9f6ed99a --- /dev/null +++ b/users/grfn/achilles/src/common/namer.rs @@ -0,0 +1,122 @@ +use std::fmt::Display; +use std::marker::PhantomData; + +pub struct Namer<T, F> { + make_name: F, + counter: u64, + _phantom: PhantomData<T>, +} + +impl<T, F> Namer<T, F> { + pub fn new(make_name: F) -> Self { + Namer { + make_name, + counter: 0, + _phantom: PhantomData, + } + } +} + +impl Namer<String, Box<dyn Fn(u64) -> String>> { + pub fn with_prefix<T>(prefix: T) -> Self + where + T: Display + 'static, + { + Namer::new(move |i| format!("{}{}", prefix, i)).boxed() + } + + pub fn with_suffix<T>(suffix: T) -> Self + where + T: Display + 'static, + { + Namer::new(move |i| format!("{}{}", i, suffix)).boxed() + } + + pub fn alphabetic() -> Self { + Namer::new(|i| { + if i <= 26 { + std::char::from_u32((i + 96) as u32).unwrap().to_string() + } else { + format!( + "{}{}", + std::char::from_u32(((i % 26) + 96) as u32).unwrap(), + i - 26 + ) + } + }) + .boxed() + } +} + +impl<T, F> Namer<T, F> +where + F: Fn(u64) -> T, +{ + pub fn make_name(&mut self) -> T { + self.counter += 1; + (self.make_name)(self.counter) + } + + pub fn boxed(self) -> NamerOf<T> + where + F: 'static, + { + Namer { + make_name: Box::new(self.make_name), + counter: self.counter, + _phantom: self._phantom, + } + } + + pub fn map<G, U>(self, f: G) -> NamerOf<U> + where + G: Fn(T) -> U + 'static, + T: 'static, + F: 'static, + { + Namer { + counter: self.counter, + make_name: Box::new(move |x| f((self.make_name)(x))), + _phantom: PhantomData, + } + } +} + +pub type NamerOf<T> = Namer<T, Box<dyn Fn(u64) -> T>>; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn prefix() { + let mut namer = Namer::with_prefix("t"); + assert_eq!(namer.make_name(), "t1"); + assert_eq!(namer.make_name(), "t2"); + } + + #[test] + fn suffix() { + let mut namer = Namer::with_suffix("t"); + assert_eq!(namer.make_name(), "1t"); + assert_eq!(namer.make_name(), "2t"); + } + + #[test] + fn alphabetic() { + let mut namer = Namer::alphabetic(); + assert_eq!(namer.make_name(), "a"); + assert_eq!(namer.make_name(), "b"); + (0..25).for_each(|_| { + namer.make_name(); + }); + assert_eq!(namer.make_name(), "b2"); + } + + #[test] + fn custom_callback() { + let mut namer = Namer::new(|n| n + 1); + assert_eq!(namer.make_name(), 2); + assert_eq!(namer.make_name(), 3); + } +} diff --git a/users/grfn/achilles/src/compiler.rs b/users/grfn/achilles/src/compiler.rs new file mode 100644 index 000000000000..45b215473d7f --- /dev/null +++ b/users/grfn/achilles/src/compiler.rs @@ -0,0 +1,89 @@ +use std::fmt::{self, Display}; +use std::path::Path; +use std::str::FromStr; +use std::{fs, result}; + +use clap::Clap; +use test_strategy::Arbitrary; + +use crate::codegen::{self, Codegen}; +use crate::common::Result; +use crate::passes::hir::{monomorphize, strip_positive_units}; +use crate::{parser, tc}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Arbitrary)] +pub enum OutputFormat { + LLVM, + Bitcode, +} + +impl Default for OutputFormat { + fn default() -> Self { + Self::Bitcode + } +} + +impl FromStr for OutputFormat { + type Err = String; + + fn from_str(s: &str) -> result::Result<Self, Self::Err> { + match s { + "llvm" => Ok(Self::LLVM), + "binary" => Ok(Self::Bitcode), + _ => Err(format!( + "Invalid output format {}, expected one of {{llvm, binary}}", + s + )), + } + } +} + +impl Display for OutputFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OutputFormat::LLVM => f.write_str("llvm"), + OutputFormat::Bitcode => f.write_str("binary"), + } + } +} + +#[derive(Clap, Debug, PartialEq, Eq, Default)] +pub struct CompilerOptions { + #[clap(long, short = 'f', default_value)] + format: OutputFormat, +} + +pub fn compile_file(input: &Path, output: &Path, options: &CompilerOptions) -> Result<()> { + let src = fs::read_to_string(input)?; + let (_, decls) = parser::toplevel(&src)?; + let mut decls = tc::typecheck_toplevel(decls)?; + monomorphize::run_toplevel(&mut decls); + strip_positive_units::run_toplevel(&mut decls); + + let context = codegen::Context::create(); + let mut codegen = Codegen::new( + &context, + &input + .file_stem() + .map_or("UNKNOWN".to_owned(), |s| s.to_string_lossy().into_owned()), + ); + for decl in &decls { + codegen.codegen_decl(decl)?; + } + match options.format { + OutputFormat::LLVM => codegen.print_to_file(output)?, + OutputFormat::Bitcode => codegen.binary_to_file(output)?, + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use test_strategy::proptest; + + #[proptest] + fn output_format_display_from_str_round_trip(of: OutputFormat) { + assert_eq!(OutputFormat::from_str(&of.to_string()), Ok(of)); + } +} diff --git a/users/grfn/achilles/src/interpreter/error.rs b/users/grfn/achilles/src/interpreter/error.rs new file mode 100644 index 000000000000..268d6f479a1e --- /dev/null +++ b/users/grfn/achilles/src/interpreter/error.rs @@ -0,0 +1,19 @@ +use std::result; + +use thiserror::Error; + +use crate::ast::{Ident, Type}; + +#[derive(Debug, PartialEq, Eq, Error)] +pub enum Error { + #[error("Undefined variable {0}")] + UndefinedVariable(Ident<'static>), + + #[error("Unexpected type {actual}, expected type {expected}")] + InvalidType { + actual: Type<'static>, + expected: Type<'static>, + }, +} + +pub type Result<T> = result::Result<T, Error>; diff --git a/users/grfn/achilles/src/interpreter/mod.rs b/users/grfn/achilles/src/interpreter/mod.rs new file mode 100644 index 000000000000..70df7a0724a5 --- /dev/null +++ b/users/grfn/achilles/src/interpreter/mod.rs @@ -0,0 +1,203 @@ +mod error; +mod value; + +use itertools::Itertools; +use value::Val; + +pub use self::error::{Error, Result}; +pub use self::value::{Function, Value}; +use crate::ast::hir::{Binding, Expr, Pattern}; +use crate::ast::{BinaryOperator, FunctionType, Ident, Literal, Type, UnaryOperator}; +use crate::common::env::Env; + +#[derive(Debug, Default)] +pub struct Interpreter<'a> { + env: Env<&'a Ident<'a>, Value<'a>>, +} + +impl<'a> Interpreter<'a> { + pub fn new() -> Self { + Self::default() + } + + fn resolve(&self, var: &'a Ident<'a>) -> Result<Value<'a>> { + self.env + .resolve(var) + .cloned() + .ok_or_else(|| Error::UndefinedVariable(var.to_owned())) + } + + fn bind_pattern(&mut self, pattern: &'a Pattern<'a, Type>, value: Value<'a>) { + match pattern { + Pattern::Id(id, _) => self.env.set(id, value), + Pattern::Tuple(pats) => { + for (pat, val) in pats.iter().zip(value.as_tuple().unwrap().clone()) { + self.bind_pattern(pat, val); + } + } + } + } + + pub fn eval(&mut self, expr: &'a Expr<'a, Type>) -> Result<Value<'a>> { + let res = match expr { + Expr::Ident(id, _) => self.resolve(id), + Expr::Literal(Literal::Int(i), _) => Ok((*i).into()), + Expr::Literal(Literal::Bool(b), _) => Ok((*b).into()), + Expr::Literal(Literal::String(s), _) => Ok(s.clone().into()), + Expr::Literal(Literal::Unit, _) => unreachable!(), + Expr::UnaryOp { op, rhs, .. } => { + let rhs = self.eval(rhs)?; + match op { + UnaryOperator::Neg => -rhs, + _ => unimplemented!(), + } + } + Expr::BinaryOp { lhs, op, rhs, .. } => { + let lhs = self.eval(lhs)?; + let rhs = self.eval(rhs)?; + match op { + BinaryOperator::Add => lhs + rhs, + BinaryOperator::Sub => lhs - rhs, + BinaryOperator::Mul => lhs * rhs, + BinaryOperator::Div => lhs / rhs, + BinaryOperator::Pow => todo!(), + BinaryOperator::Equ => Ok(lhs.eq(&rhs).into()), + BinaryOperator::Neq => todo!(), + } + } + Expr::Let { bindings, body, .. } => { + self.env.push(); + for Binding { pat, body, .. } in bindings { + let val = self.eval(body)?; + self.bind_pattern(pat, val); + } + let res = self.eval(body)?; + self.env.pop(); + Ok(res) + } + Expr::If { + condition, + then, + else_, + .. + } => { + let condition = self.eval(condition)?; + if *(condition.as_type::<bool>()?) { + self.eval(then) + } else { + self.eval(else_) + } + } + Expr::Call { ref fun, args, .. } => { + let fun = self.eval(fun)?; + let expected_type = FunctionType { + args: args.iter().map(|_| Type::Int).collect(), + ret: Box::new(Type::Int), + }; + + let Function { + args: function_args, + body, + .. + } = fun.as_function(expected_type)?; + let arg_values = function_args.iter().zip( + args.iter() + .map(|v| self.eval(v)) + .collect::<Result<Vec<_>>>()?, + ); + let mut interpreter = Interpreter::new(); + for (arg_name, arg_value) in arg_values { + interpreter.env.set(arg_name, arg_value); + } + Ok(Value::from(*interpreter.eval(body)?.as_type::<i64>()?)) + } + Expr::Fun { + type_args: _, + args, + body, + type_, + } => { + let type_ = match type_ { + Type::Function(ft) => ft.clone(), + _ => unreachable!("Function expression without function type"), + }; + + Ok(Value::from(value::Function { + // TODO + type_, + args: args.iter().map(|(arg, _)| arg.to_owned()).collect(), + body: (**body).to_owned(), + })) + } + Expr::Tuple(members, _) => Ok(Val::Tuple( + members + .into_iter() + .map(|expr| self.eval(expr)) + .try_collect()?, + ) + .into()), + }?; + debug_assert_eq!(&res.type_(), expr.type_()); + Ok(res) + } +} + +pub fn eval<'a>(expr: &'a Expr<'a, Type>) -> Result<Value<'a>> { + let mut interpreter = Interpreter::new(); + interpreter.eval(expr) +} + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use super::value::{TypeOf, Val}; + use super::*; + use BinaryOperator::*; + + fn int_lit(i: u64) -> Box<Expr<'static, Type<'static>>> { + Box::new(Expr::Literal(Literal::Int(i), Type::Int)) + } + + fn do_eval<T>(src: &str) -> T + where + for<'a> &'a T: TryFrom<&'a Val<'a>>, + T: Clone + TypeOf, + { + let expr = crate::parser::expr(src).unwrap().1; + let hir = crate::tc::typecheck_expr(expr).unwrap(); + let res = eval(&hir).unwrap(); + res.as_type::<T>().unwrap().clone() + } + + #[test] + fn simple_addition() { + let expr = Expr::BinaryOp { + lhs: int_lit(1), + op: Mul, + rhs: int_lit(2), + type_: Type::Int, + }; + let res = eval(&expr).unwrap(); + assert_eq!(*res.as_type::<i64>().unwrap(), 2); + } + + #[test] + fn variable_shadowing() { + let res = do_eval::<i64>("let x = 1 in (let x = 2 in x) + x"); + assert_eq!(res, 3); + } + + #[test] + fn conditional_with_equals() { + let res = do_eval::<i64>("let x = 1 in if x == 1 then 2 else 4"); + assert_eq!(res, 2); + } + + #[test] + #[ignore] + fn function_call() { + let res = do_eval::<i64>("let id = fn x = x in id 1"); + assert_eq!(res, 1); + } +} diff --git a/users/grfn/achilles/src/interpreter/value.rs b/users/grfn/achilles/src/interpreter/value.rs new file mode 100644 index 000000000000..272d1167a33c --- /dev/null +++ b/users/grfn/achilles/src/interpreter/value.rs @@ -0,0 +1,224 @@ +use std::borrow::Cow; +use std::convert::TryFrom; +use std::fmt::{self, Display}; +use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::rc::Rc; +use std::result; + +use derive_more::{Deref, From, TryInto}; +use itertools::Itertools; + +use super::{Error, Result}; +use crate::ast::hir::Expr; +use crate::ast::{FunctionType, Ident, Type}; + +#[derive(Debug, Clone)] +pub struct Function<'a> { + pub type_: FunctionType<'a>, + pub args: Vec<Ident<'a>>, + pub body: Expr<'a, Type<'a>>, +} + +#[derive(From, TryInto)] +#[try_into(owned, ref)] +pub enum Val<'a> { + Int(i64), + Float(f64), + Bool(bool), + String(Cow<'a, str>), + Tuple(Vec<Value<'a>>), + Function(Function<'a>), +} + +impl<'a> TryFrom<Val<'a>> for String { + type Error = (); + + fn try_from(value: Val<'a>) -> result::Result<Self, Self::Error> { + match value { + Val::String(s) => Ok(s.into_owned()), + _ => Err(()), + } + } +} + +impl<'a> fmt::Debug for Val<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Val::Int(x) => f.debug_tuple("Int").field(x).finish(), + Val::Float(x) => f.debug_tuple("Float").field(x).finish(), + Val::Bool(x) => f.debug_tuple("Bool").field(x).finish(), + Val::String(s) => f.debug_tuple("String").field(s).finish(), + Val::Function(Function { type_, .. }) => { + f.debug_struct("Function").field("type_", type_).finish() + } + Val::Tuple(members) => f.debug_tuple("Tuple").field(members).finish(), + } + } +} + +impl<'a> PartialEq for Val<'a> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Val::Int(x), Val::Int(y)) => x == y, + (Val::Float(x), Val::Float(y)) => x == y, + (Val::Bool(x), Val::Bool(y)) => x == y, + (Val::Function(_), Val::Function(_)) => false, + (_, _) => false, + } + } +} + +impl<'a> From<u64> for Val<'a> { + fn from(i: u64) -> Self { + Self::from(i as i64) + } +} + +impl<'a> Display for Val<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Val::Int(x) => x.fmt(f), + Val::Float(x) => x.fmt(f), + Val::Bool(x) => x.fmt(f), + Val::String(s) => write!(f, "{:?}", s), + Val::Function(Function { type_, .. }) => write!(f, "<{}>", type_), + Val::Tuple(members) => write!(f, "({})", members.iter().join(", ")), + } + } +} + +impl<'a> Val<'a> { + pub fn type_(&self) -> Type { + match self { + Val::Int(_) => Type::Int, + Val::Float(_) => Type::Float, + Val::Bool(_) => Type::Bool, + Val::String(_) => Type::CString, + Val::Function(Function { type_, .. }) => Type::Function(type_.clone()), + Val::Tuple(members) => Type::Tuple(members.iter().map(|expr| expr.type_()).collect()), + } + } + + pub fn as_type<'b, T>(&'b self) -> Result<&'b T> + where + T: TypeOf + 'b + Clone, + &'b T: TryFrom<&'b Self>, + { + <&T>::try_from(self).map_err(|_| Error::InvalidType { + actual: self.type_().to_owned(), + expected: <T as TypeOf>::type_of(), + }) + } + + pub fn as_function<'b>(&'b self, function_type: FunctionType) -> Result<&'b Function<'a>> { + match self { + Val::Function(f) if f.type_ == function_type => Ok(&f), + _ => Err(Error::InvalidType { + actual: self.type_().to_owned(), + expected: Type::Function(function_type.to_owned()), + }), + } + } + + pub fn as_tuple(&self) -> Option<&Vec<Value<'a>>> { + if let Self::Tuple(v) = self { + Some(v) + } else { + None + } + } + + pub fn try_into_tuple(self) -> result::Result<Vec<Value<'a>>, Self> { + if let Self::Tuple(v) = self { + Ok(v) + } else { + Err(self) + } + } +} + +#[derive(Debug, PartialEq, Clone, Deref)] +pub struct Value<'a>(Rc<Val<'a>>); + +impl<'a> Display for Value<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'a, T> From<T> for Value<'a> +where + Val<'a>: From<T>, +{ + fn from(x: T) -> Self { + Self(Rc::new(x.into())) + } +} + +impl<'a> Neg for Value<'a> { + type Output = Result<Value<'a>>; + + fn neg(self) -> Self::Output { + Ok((-self.as_type::<i64>()?).into()) + } +} + +impl<'a> Add for Value<'a> { + type Output = Result<Value<'a>>; + + fn add(self, rhs: Self) -> Self::Output { + Ok((self.as_type::<i64>()? + rhs.as_type::<i64>()?).into()) + } +} + +impl<'a> Sub for Value<'a> { + type Output = Result<Value<'a>>; + + fn sub(self, rhs: Self) -> Self::Output { + Ok((self.as_type::<i64>()? - rhs.as_type::<i64>()?).into()) + } +} + +impl<'a> Mul for Value<'a> { + type Output = Result<Value<'a>>; + + fn mul(self, rhs: Self) -> Self::Output { + Ok((self.as_type::<i64>()? * rhs.as_type::<i64>()?).into()) + } +} + +impl<'a> Div for Value<'a> { + type Output = Result<Value<'a>>; + + fn div(self, rhs: Self) -> Self::Output { + Ok((self.as_type::<f64>()? / rhs.as_type::<f64>()?).into()) + } +} + +pub trait TypeOf { + fn type_of() -> Type<'static>; +} + +impl TypeOf for i64 { + fn type_of() -> Type<'static> { + Type::Int + } +} + +impl TypeOf for bool { + fn type_of() -> Type<'static> { + Type::Bool + } +} + +impl TypeOf for f64 { + fn type_of() -> Type<'static> { + Type::Float + } +} + +impl TypeOf for String { + fn type_of() -> Type<'static> { + Type::CString + } +} diff --git a/users/grfn/achilles/src/main.rs b/users/grfn/achilles/src/main.rs new file mode 100644 index 000000000000..5ae1b59b3a8e --- /dev/null +++ b/users/grfn/achilles/src/main.rs @@ -0,0 +1,36 @@ +use clap::Clap; + +pub mod ast; +pub mod codegen; +pub(crate) mod commands; +pub(crate) mod common; +pub mod compiler; +pub mod interpreter; +pub(crate) mod passes; +#[macro_use] +pub mod parser; +pub mod tc; + +pub use common::{Error, Result}; + +#[derive(Clap)] +struct Opts { + #[clap(subcommand)] + subcommand: Command, +} + +#[derive(Clap)] +enum Command { + Eval(commands::Eval), + Compile(commands::Compile), + Check(commands::Check), +} + +fn main() -> anyhow::Result<()> { + let opts = Opts::parse(); + match opts.subcommand { + Command::Eval(eval) => Ok(eval.run()?), + Command::Compile(compile) => Ok(compile.run()?), + Command::Check(check) => Ok(check.run()?), + } +} diff --git a/users/grfn/achilles/src/parser/expr.rs b/users/grfn/achilles/src/parser/expr.rs new file mode 100644 index 000000000000..f596b18970aa --- /dev/null +++ b/users/grfn/achilles/src/parser/expr.rs @@ -0,0 +1,718 @@ +use std::borrow::Cow; + +use nom::alt; +use nom::character::complete::{digit1, multispace0, multispace1}; +use nom::{ + call, char, complete, delimited, do_parse, flat_map, many0, map, named, opt, parse_to, + preceded, separated_list0, separated_list1, tag, tuple, +}; +use pratt::{Affix, Associativity, PrattParser, Precedence}; + +use super::util::comma; +use crate::ast::{BinaryOperator, Binding, Expr, Fun, Literal, Pattern, UnaryOperator}; +use crate::parser::{arg, ident, type_}; + +#[derive(Debug)] +enum TokenTree<'a> { + Prefix(UnaryOperator), + // Postfix(char), + Infix(BinaryOperator), + Primary(Expr<'a>), + Group(Vec<TokenTree<'a>>), +} + +named!(prefix(&str) -> TokenTree, map!(alt!( + complete!(char!('-')) => { |_| UnaryOperator::Neg } | + complete!(char!('!')) => { |_| UnaryOperator::Not } +), TokenTree::Prefix)); + +named!(infix(&str) -> TokenTree, map!(alt!( + complete!(tag!("==")) => { |_| BinaryOperator::Equ } | + complete!(tag!("!=")) => { |_| BinaryOperator::Neq } | + complete!(char!('+')) => { |_| BinaryOperator::Add } | + complete!(char!('-')) => { |_| BinaryOperator::Sub } | + complete!(char!('*')) => { |_| BinaryOperator::Mul } | + complete!(char!('/')) => { |_| BinaryOperator::Div } | + complete!(char!('^')) => { |_| BinaryOperator::Pow } +), TokenTree::Infix)); + +named!(primary(&str) -> TokenTree, alt!( + do_parse!( + multispace0 >> + char!('(') >> + multispace0 >> + group: group >> + multispace0 >> + char!(')') >> + multispace0 >> + (TokenTree::Group(group)) + ) | + delimited!(multispace0, simple_expr, multispace0) => { |s| TokenTree::Primary(s) } +)); + +named!( + rest(&str) -> Vec<(TokenTree, Vec<TokenTree>, TokenTree)>, + many0!(tuple!( + infix, + delimited!(multispace0, many0!(prefix), multispace0), + primary + // many0!(postfix) + )) +); + +named!(group(&str) -> Vec<TokenTree>, do_parse!( + prefix: many0!(prefix) + >> primary: primary + // >> postfix: many0!(postfix) + >> rest: rest + >> ({ + let mut res = prefix; + res.push(primary); + // res.append(&mut postfix); + for (infix, mut prefix, primary/*, mut postfix*/) in rest { + res.push(infix); + res.append(&mut prefix); + res.push(primary); + // res.append(&mut postfix); + } + res + }) +)); + +fn token_tree(i: &str) -> nom::IResult<&str, Vec<TokenTree>> { + group(i) +} + +struct ExprParser; + +impl<'a, I> PrattParser<I> for ExprParser +where + I: Iterator<Item = TokenTree<'a>>, +{ + type Error = pratt::NoError; + type Input = TokenTree<'a>; + type Output = Expr<'a>; + + fn query(&mut self, input: &Self::Input) -> Result<Affix, Self::Error> { + use BinaryOperator::*; + use UnaryOperator::*; + + Ok(match input { + TokenTree::Infix(Add) => Affix::Infix(Precedence(6), Associativity::Left), + TokenTree::Infix(Sub) => Affix::Infix(Precedence(6), Associativity::Left), + TokenTree::Infix(Mul) => Affix::Infix(Precedence(7), Associativity::Left), + TokenTree::Infix(Div) => Affix::Infix(Precedence(7), Associativity::Left), + TokenTree::Infix(Pow) => Affix::Infix(Precedence(8), Associativity::Right), + TokenTree::Infix(Equ) => Affix::Infix(Precedence(4), Associativity::Right), + TokenTree::Infix(Neq) => Affix::Infix(Precedence(4), Associativity::Right), + TokenTree::Prefix(Neg) => Affix::Prefix(Precedence(6)), + TokenTree::Prefix(Not) => Affix::Prefix(Precedence(6)), + TokenTree::Primary(_) => Affix::Nilfix, + TokenTree::Group(_) => Affix::Nilfix, + }) + } + + fn primary(&mut self, input: Self::Input) -> Result<Self::Output, Self::Error> { + Ok(match input { + TokenTree::Primary(expr) => expr, + TokenTree::Group(group) => self.parse(&mut group.into_iter()).unwrap(), + _ => unreachable!(), + }) + } + + fn infix( + &mut self, + lhs: Self::Output, + op: Self::Input, + rhs: Self::Output, + ) -> Result<Self::Output, Self::Error> { + let op = match op { + TokenTree::Infix(op) => op, + _ => unreachable!(), + }; + Ok(Expr::BinaryOp { + lhs: Box::new(lhs), + op, + rhs: Box::new(rhs), + }) + } + + fn prefix(&mut self, op: Self::Input, rhs: Self::Output) -> Result<Self::Output, Self::Error> { + let op = match op { + TokenTree::Prefix(op) => op, + _ => unreachable!(), + }; + + Ok(Expr::UnaryOp { + op, + rhs: Box::new(rhs), + }) + } + + fn postfix( + &mut self, + _lhs: Self::Output, + _op: Self::Input, + ) -> Result<Self::Output, Self::Error> { + unreachable!() + } +} + +named!(int(&str) -> Literal, map!(flat_map!(digit1, parse_to!(u64)), Literal::Int)); + +named!(bool_(&str) -> Literal, alt!( + complete!(tag!("true")) => { |_| Literal::Bool(true) } | + complete!(tag!("false")) => { |_| Literal::Bool(false) } +)); + +fn string_internal(i: &str) -> nom::IResult<&str, Cow<'_, str>, nom::error::Error<&str>> { + // TODO(grfn): use String::split_once when that's stable + let (s, rem) = if let Some(pos) = i.find('"') { + (&i[..pos], &i[(pos + 1)..]) + } else { + return Err(nom::Err::Error(nom::error::Error::new( + i, + nom::error::ErrorKind::Tag, + ))); + }; + + Ok((rem, Cow::Borrowed(s))) +} + +named!(string(&str) -> Literal, preceded!( + complete!(char!('"')), + map!( + string_internal, + |s| Literal::String(s) + ) +)); + +named!(unit(&str) -> Literal, map!(complete!(tag!("()")), |_| Literal::Unit)); + +named!(literal(&str) -> Literal, alt!(int | bool_ | string | unit)); + +named!(literal_expr(&str) -> Expr, map!(literal, Expr::Literal)); + +named!(tuple(&str) -> Expr, do_parse!( + complete!(tag!("(")) + >> multispace0 + >> fst: expr + >> comma + >> rest: separated_list0!( + comma, + expr + ) + >> multispace0 + >> tag!(")") + >> ({ + let mut members = Vec::with_capacity(rest.len() + 1); + members.push(fst); + members.append(&mut rest.clone()); + Expr::Tuple(members) + }) +)); + +named!(tuple_pattern(&str) -> Pattern, do_parse!( + complete!(tag!("(")) + >> multispace0 + >> pats: separated_list0!( + comma, + pattern + ) + >> multispace0 + >> tag!(")") + >> (Pattern::Tuple(pats)) +)); + +named!(pattern(&str) -> Pattern, alt!( + ident => { |id| Pattern::Id(id) } | + tuple_pattern +)); + +named!(binding(&str) -> Binding, do_parse!( + multispace0 + >> pat: pattern + >> multispace0 + >> type_: opt!(preceded!(tuple!(tag!(":"), multispace0), type_)) + >> multispace0 + >> char!('=') + >> multispace0 + >> body: expr + >> (Binding { + pat, + type_, + body + }) +)); + +named!(let_(&str) -> Expr, do_parse!( + tag!("let") + >> multispace0 + >> bindings: separated_list1!(alt!(char!(';') | char!('\n')), binding) + >> multispace0 + >> tag!("in") + >> multispace0 + >> body: expr + >> (Expr::Let { + bindings, + body: Box::new(body) + }) +)); + +named!(if_(&str) -> Expr, do_parse! ( + tag!("if") + >> multispace0 + >> condition: expr + >> multispace0 + >> tag!("then") + >> multispace0 + >> then: expr + >> multispace0 + >> tag!("else") + >> multispace0 + >> else_: expr + >> (Expr::If { + condition: Box::new(condition), + then: Box::new(then), + else_: Box::new(else_) + }) +)); + +named!(ident_expr(&str) -> Expr, map!(ident, Expr::Ident)); + +fn ascripted<'a>( + p: impl Fn(&'a str) -> nom::IResult<&'a str, Expr, nom::error::Error<&'a str>> + 'a, +) -> impl Fn(&'a str) -> nom::IResult<&str, Expr, nom::error::Error<&'a str>> { + move |i| { + do_parse!( + i, + expr: p + >> multispace0 + >> complete!(tag!(":")) + >> multispace0 + >> type_: type_ + >> (Expr::Ascription { + expr: Box::new(expr), + type_ + }) + ) + } +} + +named!(paren_expr(&str) -> Expr, + delimited!(complete!(tag!("(")), expr, complete!(tag!(")")))); + +named!(funcref(&str) -> Expr, alt!( + ident_expr | + tuple | + paren_expr +)); + +named!(no_arg_call(&str) -> Expr, do_parse!( + fun: funcref + >> complete!(tag!("()")) + >> (Expr::Call { + fun: Box::new(fun), + args: vec![], + }) +)); + +named!(fun_expr(&str) -> Expr, do_parse!( + tag!("fn") + >> multispace1 + >> args: separated_list0!(multispace1, arg) + >> multispace0 + >> char!('=') + >> multispace0 + >> body: expr + >> (Expr::Fun(Box::new(Fun { + args, + body + }))) +)); + +named!(fn_arg(&str) -> Expr, alt!( + ident_expr | + literal_expr | + tuple | + paren_expr +)); + +named!(call_with_args(&str) -> Expr, do_parse!( + fun: funcref + >> multispace1 + >> args: separated_list1!(multispace1, fn_arg) + >> (Expr::Call { + fun: Box::new(fun), + args + }) +)); + +named!(simple_expr_unascripted(&str) -> Expr, alt!( + let_ | + if_ | + fun_expr | + literal_expr | + ident_expr | + tuple +)); + +named!(simple_expr(&str) -> Expr, alt!( + call!(ascripted(simple_expr_unascripted)) | + simple_expr_unascripted +)); + +named!(pub expr(&str) -> Expr, alt!( + no_arg_call | + call_with_args | + map!(token_tree, |tt| { + ExprParser.parse(&mut tt.into_iter()).unwrap() + }) | + simple_expr +)); + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::ast::{Arg, Ident, Pattern, Type}; + use std::convert::TryFrom; + use BinaryOperator::*; + use Expr::{BinaryOp, If, Let, UnaryOp}; + use UnaryOperator::*; + + pub(crate) fn ident_expr(s: &str) -> Box<Expr> { + Box::new(Expr::Ident(Ident::try_from(s).unwrap())) + } + + mod operators { + use super::*; + + #[test] + fn mul_plus() { + let (rem, res) = expr("x*y+z").unwrap(); + assert!(rem.is_empty()); + assert_eq!( + res, + BinaryOp { + lhs: Box::new(BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: ident_expr("y") + }), + op: Add, + rhs: ident_expr("z") + } + ) + } + + #[test] + fn mul_plus_ws() { + let (rem, res) = expr("x * y + z").unwrap(); + assert!(rem.is_empty(), "non-empty remainder: \"{}\"", rem); + assert_eq!( + res, + BinaryOp { + lhs: Box::new(BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: ident_expr("y") + }), + op: Add, + rhs: ident_expr("z") + } + ) + } + + #[test] + fn unary() { + let (rem, res) = expr("x * -z").unwrap(); + assert!(rem.is_empty(), "non-empty remainder: \"{}\"", rem); + assert_eq!( + res, + BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: Box::new(UnaryOp { + op: Neg, + rhs: ident_expr("z"), + }) + } + ) + } + + #[test] + fn mul_literal() { + let (rem, res) = expr("x * 3").unwrap(); + assert!(rem.is_empty()); + assert_eq!( + res, + BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: Box::new(Expr::Literal(Literal::Int(3))), + } + ) + } + + #[test] + fn equ() { + let res = test_parse!(expr, "x * 7 == 7"); + assert_eq!( + res, + BinaryOp { + lhs: Box::new(BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: Box::new(Expr::Literal(Literal::Int(7))) + }), + op: Equ, + rhs: Box::new(Expr::Literal(Literal::Int(7))) + } + ) + } + } + + #[test] + fn unit() { + assert_eq!(test_parse!(expr, "()"), Expr::Literal(Literal::Unit)); + } + + #[test] + fn bools() { + assert_eq!( + test_parse!(expr, "true"), + Expr::Literal(Literal::Bool(true)) + ); + assert_eq!( + test_parse!(expr, "false"), + Expr::Literal(Literal::Bool(false)) + ); + } + + #[test] + fn tuple() { + assert_eq!( + test_parse!(expr, "(1, \"seven\")"), + Expr::Tuple(vec![ + Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::String(Cow::Borrowed("seven"))) + ]) + ) + } + + #[test] + fn simple_string_lit() { + assert_eq!( + test_parse!(expr, "\"foobar\""), + Expr::Literal(Literal::String(Cow::Borrowed("foobar"))) + ) + } + + #[test] + fn let_complex() { + let res = test_parse!(expr, "let x = 1; y = x * 7 in (x + y) * 4"); + assert_eq!( + res, + Let { + bindings: vec![ + Binding { + pat: Pattern::Id(Ident::try_from("x").unwrap()), + type_: None, + body: Expr::Literal(Literal::Int(1)) + }, + Binding { + pat: Pattern::Id(Ident::try_from("y").unwrap()), + type_: None, + body: Expr::BinaryOp { + lhs: ident_expr("x"), + op: Mul, + rhs: Box::new(Expr::Literal(Literal::Int(7))) + } + } + ], + body: Box::new(Expr::BinaryOp { + lhs: Box::new(Expr::BinaryOp { + lhs: ident_expr("x"), + op: Add, + rhs: ident_expr("y"), + }), + op: Mul, + rhs: Box::new(Expr::Literal(Literal::Int(4))), + }) + } + ) + } + + #[test] + fn if_simple() { + let res = test_parse!(expr, "if x == 8 then 9 else 20"); + assert_eq!( + res, + If { + condition: Box::new(BinaryOp { + lhs: ident_expr("x"), + op: Equ, + rhs: Box::new(Expr::Literal(Literal::Int(8))), + }), + then: Box::new(Expr::Literal(Literal::Int(9))), + else_: Box::new(Expr::Literal(Literal::Int(20))) + } + ) + } + + #[test] + fn no_arg_call() { + let res = test_parse!(expr, "f()"); + assert_eq!( + res, + Expr::Call { + fun: ident_expr("f"), + args: vec![] + } + ); + } + + #[test] + fn unit_call() { + let res = test_parse!(expr, "f ()"); + assert_eq!( + res, + Expr::Call { + fun: ident_expr("f"), + args: vec![Expr::Literal(Literal::Unit)] + } + ) + } + + #[test] + fn call_with_args() { + let res = test_parse!(expr, "f x 1"); + assert_eq!( + res, + Expr::Call { + fun: ident_expr("f"), + args: vec![*ident_expr("x"), Expr::Literal(Literal::Int(1))] + } + ) + } + + #[test] + fn call_funcref() { + let res = test_parse!(expr, "(let x = 1 in x) 2"); + assert_eq!( + res, + Expr::Call { + fun: Box::new(Expr::Let { + bindings: vec![Binding { + pat: Pattern::Id(Ident::try_from("x").unwrap()), + type_: None, + body: Expr::Literal(Literal::Int(1)) + }], + body: ident_expr("x") + }), + args: vec![Expr::Literal(Literal::Int(2))] + } + ) + } + + #[test] + fn anon_function() { + let res = test_parse!(expr, "let id = fn x = x in id 1"); + assert_eq!( + res, + Expr::Let { + bindings: vec![Binding { + pat: Pattern::Id(Ident::try_from("id").unwrap()), + type_: None, + body: Expr::Fun(Box::new(Fun { + args: vec![Arg::try_from("x").unwrap()], + body: *ident_expr("x") + })) + }], + body: Box::new(Expr::Call { + fun: ident_expr("id"), + args: vec![Expr::Literal(Literal::Int(1))], + }) + } + ); + } + + #[test] + fn tuple_binding() { + let res = test_parse!(expr, "let (x, y) = (1, 2) in x"); + assert_eq!( + res, + Expr::Let { + bindings: vec![Binding { + pat: Pattern::Tuple(vec![ + Pattern::Id(Ident::from_str_unchecked("x")), + Pattern::Id(Ident::from_str_unchecked("y")) + ]), + body: Expr::Tuple(vec![ + Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::Int(2)) + ]), + type_: None + }], + body: Box::new(Expr::Ident(Ident::from_str_unchecked("x"))) + } + ) + } + + mod ascriptions { + use super::*; + + #[test] + fn bare_ascription() { + let res = test_parse!(expr, "1: float"); + assert_eq!( + res, + Expr::Ascription { + expr: Box::new(Expr::Literal(Literal::Int(1))), + type_: Type::Float + } + ) + } + + #[test] + fn fn_body_ascription() { + let res = test_parse!(expr, "let const_1 = fn x = 1: int in const_1 2"); + assert_eq!( + res, + Expr::Let { + bindings: vec![Binding { + pat: Pattern::Id(Ident::try_from("const_1").unwrap()), + type_: None, + body: Expr::Fun(Box::new(Fun { + args: vec![Arg::try_from("x").unwrap()], + body: Expr::Ascription { + expr: Box::new(Expr::Literal(Literal::Int(1))), + type_: Type::Int, + } + })) + }], + body: Box::new(Expr::Call { + fun: ident_expr("const_1"), + args: vec![Expr::Literal(Literal::Int(2))] + }) + } + ) + } + + #[test] + fn let_binding_ascripted() { + let res = test_parse!(expr, "let x: int = 1 in x"); + assert_eq!( + res, + Expr::Let { + bindings: vec![Binding { + pat: Pattern::Id(Ident::try_from("x").unwrap()), + type_: Some(Type::Int), + body: Expr::Literal(Literal::Int(1)) + }], + body: ident_expr("x") + } + ) + } + } +} diff --git a/users/grfn/achilles/src/parser/macros.rs b/users/grfn/achilles/src/parser/macros.rs new file mode 100644 index 000000000000..406e5c0e699e --- /dev/null +++ b/users/grfn/achilles/src/parser/macros.rs @@ -0,0 +1,16 @@ +#[cfg(test)] +#[macro_use] +macro_rules! test_parse { + ($parser: ident, $src: expr) => {{ + let res = $parser($src); + nom_trace::print_trace!(); + let (rem, res) = res.unwrap(); + assert!( + rem.is_empty(), + "non-empty remainder: \"{}\", parsed: {:?}", + rem, + res + ); + res + }}; +} diff --git a/users/grfn/achilles/src/parser/mod.rs b/users/grfn/achilles/src/parser/mod.rs new file mode 100644 index 000000000000..e088cbca10a5 --- /dev/null +++ b/users/grfn/achilles/src/parser/mod.rs @@ -0,0 +1,240 @@ +use nom::character::complete::{multispace0, multispace1}; +use nom::error::{ErrorKind, ParseError}; +use nom::{alt, char, complete, do_parse, eof, many0, named, separated_list0, tag, terminated}; + +#[macro_use] +pub(crate) mod macros; +mod expr; +mod type_; +mod util; + +use crate::ast::{Arg, Decl, Fun, Ident}; +pub use expr::expr; +use type_::function_type; +pub use type_::type_; + +pub type Error = nom::Err<nom::error::Error<String>>; + +pub(crate) fn is_reserved(s: &str) -> bool { + matches!( + s, + "if" | "then" + | "else" + | "let" + | "in" + | "fn" + | "ty" + | "int" + | "float" + | "bool" + | "true" + | "false" + | "cstring" + ) +} + +pub(crate) fn ident<'a, E>(i: &'a str) -> nom::IResult<&'a str, Ident, E> +where + E: ParseError<&'a str>, +{ + let mut chars = i.chars(); + if let Some(f) = chars.next() { + if f.is_alphabetic() || f == '_' { + let mut idx = 1; + for c in chars { + if !(c.is_alphanumeric() || c == '_') { + break; + } + idx += 1; + } + let id = &i[..idx]; + if is_reserved(id) { + Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy))) + } else { + Ok((&i[idx..], Ident::from_str_unchecked(id))) + } + } else { + Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy))) + } + } else { + Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Eof))) + } +} + +named!(ascripted_arg(&str) -> Arg, do_parse!( + complete!(char!('(')) >> + multispace0 >> + ident: ident >> + multispace0 >> + complete!(char!(':')) >> + multispace0 >> + type_: type_ >> + multispace0 >> + complete!(char!(')')) >> + (Arg { + ident, + type_: Some(type_) + }) +)); + +named!(arg(&str) -> Arg, alt!( + ident => { |ident| Arg {ident, type_: None}} | + ascripted_arg +)); + +named!(extern_decl(&str) -> Decl, do_parse!( + complete!(tag!("extern")) + >> multispace1 + >> name: ident + >> multispace0 + >> char!(':') + >> multispace0 + >> type_: function_type + >> multispace0 + >> (Decl::Extern { + name, + type_ + }) +)); + +named!(fun_decl(&str) -> Decl, do_parse!( + complete!(tag!("fn")) + >> multispace1 + >> name: ident + >> multispace1 + >> args: separated_list0!(multispace1, arg) + >> multispace0 + >> char!('=') + >> multispace0 + >> body: expr + >> (Decl::Fun { + name, + body: Fun { + args, + body + } + }) +)); + +named!(ascription_decl(&str) -> Decl, do_parse!( + complete!(tag!("ty")) + >> multispace1 + >> name: ident + >> multispace0 + >> complete!(char!(':')) + >> multispace0 + >> type_: type_ + >> multispace0 + >> (Decl::Ascription { + name, + type_ + }) +)); + +named!(pub decl(&str) -> Decl, alt!( + ascription_decl | + fun_decl | + extern_decl +)); + +named!(pub toplevel(&str) -> Vec<Decl>, do_parse!( + decls: many0!(decl) + >> multispace0 + >> eof!() + >> (decls))); + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use crate::ast::{BinaryOperator, Expr, FunctionType, Literal, Type}; + + use super::*; + use expr::tests::ident_expr; + + #[test] + fn fn_decl() { + let res = test_parse!(decl, "fn id x = x"); + assert_eq!( + res, + Decl::Fun { + name: "id".try_into().unwrap(), + body: Fun { + args: vec!["x".try_into().unwrap()], + body: *ident_expr("x"), + } + } + ) + } + + #[test] + fn ascripted_fn_args() { + test_parse!(ascripted_arg, "(x : int)"); + let res = test_parse!(decl, "fn plus1 (x : int) = x + 1"); + assert_eq!( + res, + Decl::Fun { + name: "plus1".try_into().unwrap(), + body: Fun { + args: vec![Arg { + ident: "x".try_into().unwrap(), + type_: Some(Type::Int), + }], + body: Expr::BinaryOp { + lhs: ident_expr("x"), + op: BinaryOperator::Add, + rhs: Box::new(Expr::Literal(Literal::Int(1))), + } + } + } + ); + } + + #[test] + fn multiple_decls() { + let res = test_parse!( + toplevel, + "fn id x = x + fn plus x y = x + y + fn main = plus (id 2) 7" + ); + assert_eq!(res.len(), 3); + let res = test_parse!( + toplevel, + "fn id x = x\nfn plus x y = x + y\nfn main = plus (id 2) 7\n" + ); + assert_eq!(res.len(), 3); + } + + #[test] + fn top_level_ascription() { + let res = test_parse!(toplevel, "ty id : fn a -> a"); + assert_eq!( + res, + vec![Decl::Ascription { + name: "id".try_into().unwrap(), + type_: Type::Function(FunctionType { + args: vec![Type::Var("a".try_into().unwrap())], + ret: Box::new(Type::Var("a".try_into().unwrap())) + }) + }] + ) + } + + #[test] + fn return_unit() { + assert_eq!( + test_parse!(decl, "fn g _ = ()"), + Decl::Fun { + name: "g".try_into().unwrap(), + body: Fun { + args: vec![Arg { + ident: "_".try_into().unwrap(), + type_: None, + }], + body: Expr::Literal(Literal::Unit), + }, + } + ) + } +} diff --git a/users/grfn/achilles/src/parser/type_.rs b/users/grfn/achilles/src/parser/type_.rs new file mode 100644 index 000000000000..b80f0e0860a1 --- /dev/null +++ b/users/grfn/achilles/src/parser/type_.rs @@ -0,0 +1,152 @@ +use nom::character::complete::{multispace0, multispace1}; +use nom::{alt, delimited, do_parse, map, named, opt, separated_list0, tag, terminated, tuple}; + +use super::ident; +use super::util::comma; +use crate::ast::{FunctionType, Type}; + +named!(pub function_type(&str) -> FunctionType, do_parse!( + tag!("fn") + >> multispace1 + >> args: map!(opt!(terminated!(separated_list0!( + comma, + type_ + ), multispace1)), |args| args.unwrap_or_default()) + >> tag!("->") + >> multispace1 + >> ret: type_ + >> (FunctionType { + args, + ret: Box::new(ret) + }) +)); + +named!(tuple_type(&str) -> Type, do_parse!( + tag!("(") + >> multispace0 + >> fst: type_ + >> comma + >> rest: separated_list0!( + comma, + type_ + ) + >> multispace0 + >> tag!(")") + >> ({ + let mut members = Vec::with_capacity(rest.len() + 1); + members.push(fst); + members.append(&mut rest.clone()); + Type::Tuple(members) + }) +)); + +named!(pub type_(&str) -> Type, alt!( + tag!("int") => { |_| Type::Int } | + tag!("float") => { |_| Type::Float } | + tag!("bool") => { |_| Type::Bool } | + tag!("cstring") => { |_| Type::CString } | + tag!("()") => { |_| Type::Unit } | + tuple_type | + function_type => { |ft| Type::Function(ft) }| + ident => { |id| Type::Var(id) } | + delimited!( + tuple!(tag!("("), multispace0), + type_, + tuple!(tag!(")"), multispace0) + ) +)); + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use super::*; + use crate::ast::Ident; + + #[test] + fn simple_types() { + assert_eq!(test_parse!(type_, "int"), Type::Int); + assert_eq!(test_parse!(type_, "float"), Type::Float); + assert_eq!(test_parse!(type_, "bool"), Type::Bool); + assert_eq!(test_parse!(type_, "cstring"), Type::CString); + assert_eq!(test_parse!(type_, "()"), Type::Unit); + } + + #[test] + fn no_arg_fn_type() { + assert_eq!( + test_parse!(type_, "fn -> int"), + Type::Function(FunctionType { + args: vec![], + ret: Box::new(Type::Int) + }) + ); + } + + #[test] + fn fn_type_with_args() { + assert_eq!( + test_parse!(type_, "fn int, bool -> int"), + Type::Function(FunctionType { + args: vec![Type::Int, Type::Bool], + ret: Box::new(Type::Int) + }) + ); + } + + #[test] + fn fn_taking_fn() { + assert_eq!( + test_parse!(type_, "fn fn int, bool -> bool, float -> float"), + Type::Function(FunctionType { + args: vec![ + Type::Function(FunctionType { + args: vec![Type::Int, Type::Bool], + ret: Box::new(Type::Bool) + }), + Type::Float + ], + ret: Box::new(Type::Float) + }) + ) + } + + #[test] + fn parenthesized() { + assert_eq!( + test_parse!(type_, "fn (fn int, bool -> bool), float -> float"), + Type::Function(FunctionType { + args: vec![ + Type::Function(FunctionType { + args: vec![Type::Int, Type::Bool], + ret: Box::new(Type::Bool) + }), + Type::Float + ], + ret: Box::new(Type::Float) + }) + ) + } + + #[test] + fn tuple() { + assert_eq!( + test_parse!(type_, "(int, int)"), + Type::Tuple(vec![Type::Int, Type::Int]) + ) + } + + #[test] + fn type_vars() { + assert_eq!( + test_parse!(type_, "fn x, y -> x"), + Type::Function(FunctionType { + args: vec![ + Type::Var(Ident::try_from("x").unwrap()), + Type::Var(Ident::try_from("y").unwrap()), + ], + ret: Box::new(Type::Var(Ident::try_from("x").unwrap())), + }) + ) + } +} diff --git a/users/grfn/achilles/src/parser/util.rs b/users/grfn/achilles/src/parser/util.rs new file mode 100644 index 000000000000..bb53fb7fff50 --- /dev/null +++ b/users/grfn/achilles/src/parser/util.rs @@ -0,0 +1,8 @@ +use nom::character::complete::multispace0; +use nom::{complete, map, named, tag, tuple}; + +named!(pub(crate) comma(&str) -> (), map!(tuple!( + multispace0, + complete!(tag!(",")), + multispace0 +) ,|_| ())); diff --git a/users/grfn/achilles/src/passes/hir/mod.rs b/users/grfn/achilles/src/passes/hir/mod.rs new file mode 100644 index 000000000000..872c449eb020 --- /dev/null +++ b/users/grfn/achilles/src/passes/hir/mod.rs @@ -0,0 +1,211 @@ +use std::collections::HashMap; + +use crate::ast::hir::{Binding, Decl, Expr, Pattern}; +use crate::ast::{BinaryOperator, Ident, Literal, UnaryOperator}; + +pub(crate) mod monomorphize; +pub(crate) mod strip_positive_units; + +pub(crate) trait Visitor<'a, 'ast, T: 'ast>: Sized + 'a { + type Error; + + fn visit_type(&mut self, _type: &mut T) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_ident(&mut self, _ident: &mut Ident<'ast>) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_literal(&mut self, _literal: &mut Literal<'ast>) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_unary_operator(&mut self, _op: &mut UnaryOperator) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_binary_operator(&mut self, _op: &mut BinaryOperator) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_pattern(&mut self, _pat: &mut Pattern<'ast, T>) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_binding(&mut self, binding: &mut Binding<'ast, T>) -> Result<(), Self::Error> { + self.visit_pattern(&mut binding.pat)?; + self.visit_expr(&mut binding.body)?; + Ok(()) + } + + fn post_visit_call( + &mut self, + _fun: &mut Expr<'ast, T>, + _type_args: &mut HashMap<Ident<'ast>, T>, + _args: &mut Vec<Expr<'ast, T>>, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn pre_visit_call( + &mut self, + _fun: &mut Expr<'ast, T>, + _type_args: &mut HashMap<Ident<'ast>, T>, + _args: &mut Vec<Expr<'ast, T>>, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_tuple(&mut self, members: &mut Vec<Expr<'ast, T>>) -> Result<(), Self::Error> { + for expr in members { + self.visit_expr(expr)?; + } + Ok(()) + } + + fn pre_visit_expr(&mut self, _expr: &mut Expr<'ast, T>) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_expr(&mut self, expr: &mut Expr<'ast, T>) -> Result<(), Self::Error> { + self.pre_visit_expr(expr)?; + match expr { + Expr::Ident(id, t) => { + self.visit_ident(id)?; + self.visit_type(t)?; + } + Expr::Literal(lit, t) => { + self.visit_literal(lit)?; + self.visit_type(t)?; + } + Expr::UnaryOp { op, rhs, type_ } => { + self.visit_unary_operator(op)?; + self.visit_expr(rhs)?; + self.visit_type(type_)?; + } + Expr::BinaryOp { + lhs, + op, + rhs, + type_, + } => { + self.visit_expr(lhs)?; + self.visit_binary_operator(op)?; + self.visit_expr(rhs)?; + self.visit_type(type_)?; + } + Expr::Let { + bindings, + body, + type_, + } => { + for binding in bindings.iter_mut() { + self.visit_binding(binding)?; + } + self.visit_expr(body)?; + self.visit_type(type_)?; + } + Expr::If { + condition, + then, + else_, + type_, + } => { + self.visit_expr(condition)?; + self.visit_expr(then)?; + self.visit_expr(else_)?; + self.visit_type(type_)?; + } + Expr::Fun { + args, + body, + type_args, + type_, + } => { + for (ident, t) in args { + self.visit_ident(ident)?; + self.visit_type(t)?; + } + for ta in type_args { + self.visit_ident(ta)?; + } + self.visit_expr(body)?; + self.visit_type(type_)?; + } + Expr::Call { + fun, + args, + type_args, + type_, + } => { + self.pre_visit_call(fun, type_args, args)?; + self.visit_expr(fun)?; + for arg in args.iter_mut() { + self.visit_expr(arg)?; + } + self.visit_type(type_)?; + self.post_visit_call(fun, type_args, args)?; + } + Expr::Tuple(tup, type_) => { + self.visit_tuple(tup)?; + self.visit_type(type_)?; + } + } + + Ok(()) + } + + fn post_visit_decl(&mut self, _decl: &'a Decl<'ast, T>) -> Result<(), Self::Error> { + Ok(()) + } + + fn post_visit_fun_decl( + &mut self, + _name: &mut Ident<'ast>, + _type_args: &mut Vec<Ident>, + _args: &mut Vec<(Ident, T)>, + _body: &mut Box<Expr<T>>, + _type_: &mut T, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn visit_decl(&mut self, decl: &'a mut Decl<'ast, T>) -> Result<(), Self::Error> { + match decl { + Decl::Fun { + name, + type_args, + args, + body, + type_, + } => { + self.visit_ident(name)?; + for type_arg in type_args.iter_mut() { + self.visit_ident(type_arg)?; + } + for (arg, t) in args.iter_mut() { + self.visit_ident(arg)?; + self.visit_type(t)?; + } + self.visit_expr(body)?; + self.visit_type(type_)?; + self.post_visit_fun_decl(name, type_args, args, body, type_)?; + } + Decl::Extern { + name, + arg_types, + ret_type, + } => { + self.visit_ident(name)?; + for arg_t in arg_types { + self.visit_type(arg_t)?; + } + self.visit_type(ret_type)?; + } + } + + self.post_visit_decl(decl)?; + Ok(()) + } +} diff --git a/users/grfn/achilles/src/passes/hir/monomorphize.rs b/users/grfn/achilles/src/passes/hir/monomorphize.rs new file mode 100644 index 000000000000..251a988f4f6f --- /dev/null +++ b/users/grfn/achilles/src/passes/hir/monomorphize.rs @@ -0,0 +1,139 @@ +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; +use std::mem; + +use void::{ResultVoidExt, Void}; + +use crate::ast::hir::{Decl, Expr}; +use crate::ast::{self, Ident}; + +use super::Visitor; + +#[derive(Default)] +pub(crate) struct Monomorphize<'a, 'ast> { + decls: HashMap<&'a Ident<'ast>, &'a Decl<'ast, ast::Type<'ast>>>, + extra_decls: Vec<Decl<'ast, ast::Type<'ast>>>, + remove_decls: HashSet<Ident<'ast>>, +} + +impl<'a, 'ast> Monomorphize<'a, 'ast> { + pub(crate) fn new() -> Self { + Default::default() + } +} + +impl<'a, 'ast> Visitor<'a, 'ast, ast::Type<'ast>> for Monomorphize<'a, 'ast> { + type Error = Void; + + fn post_visit_call( + &mut self, + fun: &mut Expr<'ast, ast::Type<'ast>>, + type_args: &mut HashMap<Ident<'ast>, ast::Type<'ast>>, + args: &mut Vec<Expr<'ast, ast::Type<'ast>>>, + ) -> Result<(), Self::Error> { + let new_fun = match fun { + Expr::Ident(id, _) => { + let decl: Decl<_> = (**self.decls.get(id).unwrap()).clone(); + let name = RefCell::new(id.to_string()); + let type_args = mem::take(type_args); + let mut monomorphized = decl + .traverse_type(|ty| -> Result<_, Void> { + Ok(ty.clone().traverse_type_vars(|v| { + let concrete = type_args.get(&v).unwrap(); + name.borrow_mut().push_str(&concrete.to_string()); + concrete.clone() + })) + }) + .void_unwrap(); + let name: Ident = name.into_inner().try_into().unwrap(); + if name != *id { + self.remove_decls.insert(id.clone()); + monomorphized.set_name(name.clone()); + let type_ = monomorphized.type_().unwrap().clone(); + self.extra_decls.push(monomorphized); + Some(Expr::Ident(name, type_)) + } else { + None + } + } + _ => todo!(), + }; + if let Some(new_fun) = new_fun { + *fun = new_fun; + } + Ok(()) + } + + fn post_visit_decl( + &mut self, + decl: &'a Decl<'ast, ast::Type<'ast>>, + ) -> Result<(), Self::Error> { + self.decls.insert(decl.name(), decl); + Ok(()) + } +} + +pub(crate) fn run_toplevel<'a>(toplevel: &mut Vec<Decl<'a, ast::Type<'a>>>) { + let mut pass = Monomorphize::new(); + for decl in toplevel.iter_mut() { + pass.visit_decl(decl).void_unwrap(); + } + let remove_decls = mem::take(&mut pass.remove_decls); + let mut extra_decls = mem::take(&mut pass.extra_decls); + toplevel.retain(|decl| !remove_decls.contains(decl.name())); + extra_decls.append(toplevel); + *toplevel = extra_decls; +} + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use super::*; + use crate::parser::toplevel; + use crate::tc::typecheck_toplevel; + + #[test] + fn call_id_decl() { + let (_, program) = toplevel( + "ty id : fn a -> a + fn id x = x + + ty main : fn -> int + fn main = id 0", + ) + .unwrap(); + let mut program = typecheck_toplevel(program).unwrap(); + run_toplevel(&mut program); + + let find_decl = |ident: &str| { + program.iter().find(|decl| { + matches!(decl, Decl::Fun {name, ..} if name == &Ident::try_from(ident).unwrap()) + }).unwrap() + }; + + let main = find_decl("main"); + let body = match main { + Decl::Fun { body, .. } => body, + _ => unreachable!(), + }; + + let expected_type = ast::Type::Function(ast::FunctionType { + args: vec![ast::Type::Int], + ret: Box::new(ast::Type::Int), + }); + + match &**body { + Expr::Call { fun, .. } => { + let fun = match &**fun { + Expr::Ident(fun, _) => fun, + _ => unreachable!(), + }; + let called_decl = find_decl(fun.into()); + assert_eq!(called_decl.type_().unwrap(), &expected_type); + } + _ => unreachable!(), + } + } +} diff --git a/users/grfn/achilles/src/passes/hir/strip_positive_units.rs b/users/grfn/achilles/src/passes/hir/strip_positive_units.rs new file mode 100644 index 000000000000..85ee1cce4859 --- /dev/null +++ b/users/grfn/achilles/src/passes/hir/strip_positive_units.rs @@ -0,0 +1,191 @@ +use std::collections::HashMap; +use std::mem; + +use ast::hir::{Binding, Pattern}; +use ast::Literal; +use void::{ResultVoidExt, Void}; + +use crate::ast::hir::{Decl, Expr}; +use crate::ast::{self, Ident}; + +use super::Visitor; + +/// Strip all values with a unit type in positive (non-return) position +pub(crate) struct StripPositiveUnits {} + +impl<'a, 'ast> Visitor<'a, 'ast, ast::Type<'ast>> for StripPositiveUnits { + type Error = Void; + + fn pre_visit_expr( + &mut self, + expr: &mut Expr<'ast, ast::Type<'ast>>, + ) -> Result<(), Self::Error> { + let mut extracted = vec![]; + if let Expr::Call { args, .. } = expr { + // TODO(grfn): replace with drain_filter once it's stabilized + let mut i = 0; + while i != args.len() { + if args[i].type_() == &ast::Type::Unit { + let expr = args.remove(i); + if !matches!(expr, Expr::Literal(Literal::Unit, _)) { + extracted.push(expr) + }; + } else { + i += 1 + } + } + } + + if !extracted.is_empty() { + let body = mem::replace(expr, Expr::Literal(Literal::Unit, ast::Type::Unit)); + *expr = Expr::Let { + bindings: extracted + .into_iter() + .map(|expr| Binding { + pat: Pattern::Id( + Ident::from_str_unchecked("___discarded"), + expr.type_().clone(), + ), + body: expr, + }) + .collect(), + type_: body.type_().clone(), + body: Box::new(body), + }; + } + + Ok(()) + } + + fn post_visit_call( + &mut self, + _fun: &mut Expr<'ast, ast::Type<'ast>>, + _type_args: &mut HashMap<Ident<'ast>, ast::Type<'ast>>, + args: &mut Vec<Expr<'ast, ast::Type<'ast>>>, + ) -> Result<(), Self::Error> { + args.retain(|arg| arg.type_() != &ast::Type::Unit); + Ok(()) + } + + fn visit_type(&mut self, type_: &mut ast::Type<'ast>) -> Result<(), Self::Error> { + if let ast::Type::Function(ft) = type_ { + ft.args.retain(|a| a != &ast::Type::Unit); + } + Ok(()) + } + + fn post_visit_fun_decl( + &mut self, + _name: &mut Ident<'ast>, + _type_args: &mut Vec<Ident>, + args: &mut Vec<(Ident, ast::Type<'ast>)>, + _body: &mut Box<Expr<ast::Type<'ast>>>, + _type_: &mut ast::Type<'ast>, + ) -> Result<(), Self::Error> { + args.retain(|(_, ty)| ty != &ast::Type::Unit); + Ok(()) + } +} + +pub(crate) fn run_toplevel<'a>(toplevel: &mut Vec<Decl<'a, ast::Type<'a>>>) { + let mut pass = StripPositiveUnits {}; + for decl in toplevel.iter_mut() { + pass.visit_decl(decl).void_unwrap(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::toplevel; + use crate::tc::typecheck_toplevel; + use pretty_assertions::assert_eq; + + #[test] + fn unit_only_arg() { + let (_, program) = toplevel( + "ty f : fn () -> int + fn f _ = 1 + + ty main : fn -> int + fn main = f ()", + ) + .unwrap(); + + let (_, expected) = toplevel( + "ty f : fn -> int + fn f = 1 + + ty main : fn -> int + fn main = f()", + ) + .unwrap(); + let expected = typecheck_toplevel(expected).unwrap(); + + let mut program = typecheck_toplevel(program).unwrap(); + run_toplevel(&mut program); + + assert_eq!(program, expected); + } + + #[test] + fn unit_and_other_arg() { + let (_, program) = toplevel( + "ty f : fn (), int -> int + fn f _ x = x + + ty main : fn -> int + fn main = f () 1", + ) + .unwrap(); + + let (_, expected) = toplevel( + "ty f : fn int -> int + fn f x = x + + ty main : fn -> int + fn main = f 1", + ) + .unwrap(); + let expected = typecheck_toplevel(expected).unwrap(); + + let mut program = typecheck_toplevel(program).unwrap(); + run_toplevel(&mut program); + + assert_eq!(program, expected); + } + + #[test] + fn unit_expr_and_other_arg() { + let (_, program) = toplevel( + "ty f : fn (), int -> int + fn f _ x = x + + ty g : fn int -> () + fn g _ = () + + ty main : fn -> int + fn main = f (g 2) 1", + ) + .unwrap(); + + let (_, expected) = toplevel( + "ty f : fn int -> int + fn f x = x + + ty g : fn int -> () + fn g _ = () + + ty main : fn -> int + fn main = let ___discarded = g 2 in f 1", + ) + .unwrap(); + assert_eq!(expected.len(), 6); + let expected = typecheck_toplevel(expected).unwrap(); + + let mut program = typecheck_toplevel(program).unwrap(); + run_toplevel(&mut program); + + assert_eq!(program, expected); + } +} diff --git a/users/grfn/achilles/src/passes/mod.rs b/users/grfn/achilles/src/passes/mod.rs new file mode 100644 index 000000000000..306869bef1d5 --- /dev/null +++ b/users/grfn/achilles/src/passes/mod.rs @@ -0,0 +1 @@ +pub(crate) mod hir; diff --git a/users/grfn/achilles/src/tc/mod.rs b/users/grfn/achilles/src/tc/mod.rs new file mode 100644 index 000000000000..5825bab1fbe9 --- /dev/null +++ b/users/grfn/achilles/src/tc/mod.rs @@ -0,0 +1,808 @@ +use bimap::BiMap; +use derive_more::From; +use itertools::Itertools; +use std::cell::RefCell; +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::fmt::{self, Display}; +use std::{mem, result}; +use thiserror::Error; + +use crate::ast::{self, hir, Arg, BinaryOperator, Ident, Literal, Pattern}; +use crate::common::env::Env; +use crate::common::{Namer, NamerOf}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Undefined variable {0}")] + UndefinedVariable(Ident<'static>), + + #[error("Mismatched types: expected {expected}, but got {actual}")] + TypeMismatch { expected: Type, actual: Type }, + + #[error("Mismatched types, expected numeric type, but got {0}")] + NonNumeric(Type), + + #[error("Ambiguous type {0}")] + AmbiguousType(TyVar), +} + +pub type Result<T> = result::Result<T, Error>; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct TyVar(u64); + +impl Display for TyVar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "t{}", self.0) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct NullaryType(String); + +impl Display for NullaryType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PrimType { + Int, + Float, + Bool, + CString, +} + +impl<'a> From<PrimType> for ast::Type<'a> { + fn from(pr: PrimType) -> Self { + match pr { + PrimType::Int => ast::Type::Int, + PrimType::Float => ast::Type::Float, + PrimType::Bool => ast::Type::Bool, + PrimType::CString => ast::Type::CString, + } + } +} + +impl Display for PrimType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PrimType::Int => f.write_str("int"), + PrimType::Float => f.write_str("float"), + PrimType::Bool => f.write_str("bool"), + PrimType::CString => f.write_str("cstring"), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, From)] +pub enum Type { + #[from(ignore)] + Univ(TyVar), + #[from(ignore)] + Exist(TyVar), + Nullary(NullaryType), + Prim(PrimType), + Tuple(Vec<Type>), + Unit, + Fun { + args: Vec<Type>, + ret: Box<Type>, + }, +} + +impl<'a> TryFrom<Type> for ast::Type<'a> { + type Error = Type; + + fn try_from(value: Type) -> result::Result<Self, Self::Error> { + match value { + Type::Unit => Ok(ast::Type::Unit), + Type::Univ(_) => todo!(), + Type::Exist(_) => Err(value), + Type::Nullary(_) => todo!(), + Type::Prim(p) => Ok(p.into()), + Type::Tuple(members) => Ok(ast::Type::Tuple( + members.into_iter().map(|ty| ty.try_into()).try_collect()?, + )), + Type::Fun { ref args, ref ret } => Ok(ast::Type::Function(ast::FunctionType { + args: args + .clone() + .into_iter() + .map(Self::try_from) + .try_collect() + .map_err(|_| value.clone())?, + ret: Box::new((*ret.clone()).try_into().map_err(|_| value.clone())?), + })), + } + } +} + +const INT: Type = Type::Prim(PrimType::Int); +const FLOAT: Type = Type::Prim(PrimType::Float); +const BOOL: Type = Type::Prim(PrimType::Bool); +const CSTRING: Type = Type::Prim(PrimType::CString); + +impl Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Nullary(nt) => nt.fmt(f), + Type::Prim(p) => p.fmt(f), + Type::Univ(TyVar(n)) => write!(f, "โ{}", n), + Type::Exist(TyVar(n)) => write!(f, "โ{}", n), + Type::Fun { args, ret } => write!(f, "fn {} -> {}", args.iter().join(", "), ret), + Type::Tuple(members) => write!(f, "({})", members.iter().join(", ")), + Type::Unit => write!(f, "()"), + } + } +} + +struct Typechecker<'ast> { + ty_var_namer: NamerOf<TyVar>, + ctx: HashMap<TyVar, Type>, + env: Env<Ident<'ast>, Type>, + + /// AST type var -> type + instantiations: Env<Ident<'ast>, Type>, + + /// AST type-var -> universal TyVar + type_vars: RefCell<(BiMap<Ident<'ast>, TyVar>, NamerOf<Ident<'static>>)>, +} + +impl<'ast> Typechecker<'ast> { + fn new() -> Self { + Self { + ty_var_namer: Namer::new(TyVar).boxed(), + type_vars: RefCell::new(( + Default::default(), + Namer::alphabetic().map(|n| Ident::try_from(n).unwrap()), + )), + ctx: Default::default(), + env: Default::default(), + instantiations: Default::default(), + } + } + + fn bind_pattern( + &mut self, + pat: Pattern<'ast>, + type_: Type, + ) -> Result<hir::Pattern<'ast, Type>> { + match pat { + Pattern::Id(ident) => { + self.env.set(ident.clone(), type_.clone()); + Ok(hir::Pattern::Id(ident, type_)) + } + Pattern::Tuple(members) => { + let mut tys = Vec::with_capacity(members.len()); + let mut hir_members = Vec::with_capacity(members.len()); + for pat in members { + let ty = self.fresh_ex(); + hir_members.push(self.bind_pattern(pat, ty.clone())?); + tys.push(ty); + } + let tuple_type = Type::Tuple(tys); + self.unify(&tuple_type, &type_)?; + Ok(hir::Pattern::Tuple(hir_members)) + } + } + } + + pub(crate) fn tc_expr(&mut self, expr: ast::Expr<'ast>) -> Result<hir::Expr<'ast, Type>> { + match expr { + ast::Expr::Ident(ident) => { + let type_ = self + .env + .resolve(&ident) + .ok_or_else(|| Error::UndefinedVariable(ident.to_owned()))? + .clone(); + Ok(hir::Expr::Ident(ident, type_)) + } + ast::Expr::Literal(lit) => { + let type_ = match lit { + Literal::Int(_) => Type::Prim(PrimType::Int), + Literal::Bool(_) => Type::Prim(PrimType::Bool), + Literal::String(_) => Type::Prim(PrimType::CString), + Literal::Unit => Type::Unit, + }; + Ok(hir::Expr::Literal(lit.to_owned(), type_)) + } + ast::Expr::Tuple(members) => { + let members = members + .into_iter() + .map(|expr| self.tc_expr(expr)) + .collect::<Result<Vec<_>>>()?; + let type_ = Type::Tuple(members.iter().map(|expr| expr.type_().clone()).collect()); + Ok(hir::Expr::Tuple(members, type_)) + } + ast::Expr::UnaryOp { op, rhs } => todo!(), + ast::Expr::BinaryOp { lhs, op, rhs } => { + let lhs = self.tc_expr(*lhs)?; + let rhs = self.tc_expr(*rhs)?; + let type_ = match op { + BinaryOperator::Equ | BinaryOperator::Neq => { + self.unify(lhs.type_(), rhs.type_())?; + Type::Prim(PrimType::Bool) + } + BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul => { + let ty = self.unify(lhs.type_(), rhs.type_())?; + // if !matches!(ty, Type::Int | Type::Float) { + // return Err(Error::NonNumeric(ty)); + // } + ty + } + BinaryOperator::Div => todo!(), + BinaryOperator::Pow => todo!(), + }; + Ok(hir::Expr::BinaryOp { + lhs: Box::new(lhs), + op, + rhs: Box::new(rhs), + type_, + }) + } + ast::Expr::Let { bindings, body } => { + self.env.push(); + let bindings = bindings + .into_iter() + .map( + |ast::Binding { pat, type_, body }| -> Result<hir::Binding<Type>> { + let body = self.tc_expr(body)?; + if let Some(type_) = type_ { + let type_ = self.type_from_ast_type(type_); + self.unify(body.type_(), &type_)?; + } + let pat = self.bind_pattern(pat, body.type_().clone())?; + Ok(hir::Binding { pat, body }) + }, + ) + .collect::<Result<Vec<hir::Binding<Type>>>>()?; + let body = self.tc_expr(*body)?; + self.env.pop(); + Ok(hir::Expr::Let { + bindings, + type_: body.type_().clone(), + body: Box::new(body), + }) + } + ast::Expr::If { + condition, + then, + else_, + } => { + let condition = self.tc_expr(*condition)?; + self.unify(&Type::Prim(PrimType::Bool), condition.type_())?; + let then = self.tc_expr(*then)?; + let else_ = self.tc_expr(*else_)?; + let type_ = self.unify(then.type_(), else_.type_())?; + Ok(hir::Expr::If { + condition: Box::new(condition), + then: Box::new(then), + else_: Box::new(else_), + type_, + }) + } + ast::Expr::Fun(f) => { + let ast::Fun { args, body } = *f; + self.env.push(); + let args: Vec<_> = args + .into_iter() + .map(|Arg { ident, type_ }| { + let ty = match type_ { + Some(t) => self.type_from_ast_type(t), + None => self.fresh_ex(), + }; + self.env.set(ident.clone(), ty.clone()); + (ident, ty) + }) + .collect(); + let body = self.tc_expr(body)?; + self.env.pop(); + Ok(hir::Expr::Fun { + type_: Type::Fun { + args: args.iter().map(|(_, ty)| ty.clone()).collect(), + ret: Box::new(body.type_().clone()), + }, + type_args: vec![], // TODO fill in once we do let generalization + args, + body: Box::new(body), + }) + } + ast::Expr::Call { fun, args } => { + let ret_ty = self.fresh_ex(); + let arg_tys = args.iter().map(|_| self.fresh_ex()).collect::<Vec<_>>(); + let ft = Type::Fun { + args: arg_tys.clone(), + ret: Box::new(ret_ty.clone()), + }; + let fun = self.tc_expr(*fun)?; + self.instantiations.push(); + self.unify(&ft, fun.type_())?; + let args = args + .into_iter() + .zip(arg_tys) + .map(|(arg, ty)| { + let arg = self.tc_expr(arg)?; + self.unify(&ty, arg.type_())?; + Ok(arg) + }) + .try_collect()?; + let type_args = self.commit_instantiations(); + Ok(hir::Expr::Call { + fun: Box::new(fun), + type_args, + args, + type_: ret_ty, + }) + } + ast::Expr::Ascription { expr, type_ } => { + let expr = self.tc_expr(*expr)?; + let type_ = self.type_from_ast_type(type_); + self.unify(expr.type_(), &type_)?; + Ok(expr) + } + } + } + + pub(crate) fn tc_decl( + &mut self, + decl: ast::Decl<'ast>, + ) -> Result<Option<hir::Decl<'ast, Type>>> { + match decl { + ast::Decl::Fun { name, body } => { + let mut expr = ast::Expr::Fun(Box::new(body)); + if let Some(type_) = self.env.resolve(&name) { + expr = ast::Expr::Ascription { + expr: Box::new(expr), + type_: self.finalize_type(type_.clone())?, + }; + } + + self.env.push(); + let body = self.tc_expr(expr)?; + let type_ = body.type_().clone(); + self.env.set(name.clone(), type_); + self.env.pop(); + match body { + hir::Expr::Fun { + type_args, + args, + body, + type_, + } => Ok(Some(hir::Decl::Fun { + name, + type_args, + args, + body, + type_, + })), + _ => unreachable!(), + } + } + ast::Decl::Ascription { name, type_ } => { + let type_ = self.type_from_ast_type(type_); + self.env.set(name.clone(), type_); + Ok(None) + } + ast::Decl::Extern { name, type_ } => { + let type_ = self.type_from_ast_type(ast::Type::Function(type_)); + self.env.set(name.clone(), type_.clone()); + let (arg_types, ret_type) = match type_ { + Type::Fun { args, ret } => (args, *ret), + _ => unreachable!(), + }; + Ok(Some(hir::Decl::Extern { + name, + arg_types, + ret_type, + })) + } + } + } + + fn fresh_tv(&mut self) -> TyVar { + self.ty_var_namer.make_name() + } + + fn fresh_ex(&mut self) -> Type { + Type::Exist(self.fresh_tv()) + } + + fn fresh_univ(&mut self) -> Type { + Type::Univ(self.fresh_tv()) + } + + fn unify(&mut self, ty1: &Type, ty2: &Type) -> Result<Type> { + match (ty1, ty2) { + (Type::Unit, Type::Unit) => Ok(Type::Unit), + (Type::Exist(tv), ty) | (ty, Type::Exist(tv)) => match self.resolve_tv(*tv)? { + Some(existing_ty) if self.types_match(ty, &existing_ty) => Ok(ty.clone()), + Some(var @ ast::Type::Var(_)) => { + let var = self.type_from_ast_type(var); + self.unify(&var, ty) + } + Some(existing_ty) => match ty { + Type::Exist(_) => { + let rhs = self.type_from_ast_type(existing_ty); + self.unify(ty, &rhs) + } + _ => Err(Error::TypeMismatch { + expected: ty.clone(), + actual: self.type_from_ast_type(existing_ty), + }), + }, + None => match self.ctx.insert(*tv, ty.clone()) { + Some(existing) => self.unify(&existing, ty), + None => Ok(ty.clone()), + }, + }, + (Type::Univ(u1), Type::Univ(u2)) if u1 == u2 => Ok(ty2.clone()), + (Type::Univ(u), ty) | (ty, Type::Univ(u)) => { + let ident = self.name_univ(*u); + match self.instantiations.resolve(&ident) { + Some(existing_ty) if ty == existing_ty => Ok(ty.clone()), + Some(existing_ty) => Err(Error::TypeMismatch { + expected: ty.clone(), + actual: existing_ty.clone(), + }), + None => { + self.instantiations.set(ident, ty.clone()); + Ok(ty.clone()) + } + } + } + (Type::Prim(p1), Type::Prim(p2)) if p1 == p2 => Ok(ty2.clone()), + (Type::Tuple(t1), Type::Tuple(t2)) if t1.len() == t2.len() => { + let ts = t1 + .iter() + .zip(t2.iter()) + .map(|(t1, t2)| self.unify(t1, t2)) + .try_collect()?; + Ok(Type::Tuple(ts)) + } + ( + Type::Fun { + args: args1, + ret: ret1, + }, + Type::Fun { + args: args2, + ret: ret2, + }, + ) => { + let args = args1 + .iter() + .zip(args2) + .map(|(t1, t2)| self.unify(t1, t2)) + .try_collect()?; + let ret = self.unify(ret1, ret2)?; + Ok(Type::Fun { + args, + ret: Box::new(ret), + }) + } + (Type::Nullary(_), _) | (_, Type::Nullary(_)) => todo!(), + _ => Err(Error::TypeMismatch { + expected: ty1.clone(), + actual: ty2.clone(), + }), + } + } + + fn finalize_expr( + &self, + expr: hir::Expr<'ast, Type>, + ) -> Result<hir::Expr<'ast, ast::Type<'ast>>> { + expr.traverse_type(|ty| self.finalize_type(ty)) + } + + fn finalize_decl( + &mut self, + decl: hir::Decl<'ast, Type>, + ) -> Result<hir::Decl<'ast, ast::Type<'ast>>> { + let res = decl.traverse_type(|ty| self.finalize_type(ty))?; + if let Some(type_) = res.type_() { + let ty = self.type_from_ast_type(type_.clone()); + self.env.set(res.name().clone(), ty); + } + Ok(res) + } + + fn finalize_type(&self, ty: Type) -> Result<ast::Type<'static>> { + let ret = match ty { + Type::Exist(tv) => self.resolve_tv(tv)?.ok_or(Error::AmbiguousType(tv)), + Type::Univ(tv) => Ok(ast::Type::Var(self.name_univ(tv))), + Type::Unit => Ok(ast::Type::Unit), + Type::Nullary(_) => todo!(), + Type::Prim(pr) => Ok(pr.into()), + Type::Tuple(members) => Ok(ast::Type::Tuple( + members + .into_iter() + .map(|ty| self.finalize_type(ty)) + .try_collect()?, + )), + Type::Fun { args, ret } => Ok(ast::Type::Function(ast::FunctionType { + args: args + .into_iter() + .map(|ty| self.finalize_type(ty)) + .try_collect()?, + ret: Box::new(self.finalize_type(*ret)?), + })), + }; + ret + } + + fn resolve_tv(&self, tv: TyVar) -> Result<Option<ast::Type<'static>>> { + let mut res = &Type::Exist(tv); + Ok(loop { + match res { + Type::Exist(tv) => { + res = match self.ctx.get(tv) { + Some(r) => r, + None => return Ok(None), + }; + } + Type::Univ(tv) => { + let ident = self.name_univ(*tv); + if let Some(r) = self.instantiations.resolve(&ident) { + res = r; + } else { + break Some(ast::Type::Var(ident)); + } + } + Type::Nullary(_) => todo!(), + Type::Prim(pr) => break Some((*pr).into()), + Type::Unit => break Some(ast::Type::Unit), + Type::Fun { args, ret } => todo!(), + Type::Tuple(_) => break Some(self.finalize_type(res.clone())?), + } + }) + } + + fn type_from_ast_type(&mut self, ast_type: ast::Type<'ast>) -> Type { + match ast_type { + ast::Type::Unit => Type::Unit, + ast::Type::Int => INT, + ast::Type::Float => FLOAT, + ast::Type::Bool => BOOL, + ast::Type::CString => CSTRING, + ast::Type::Tuple(members) => Type::Tuple( + members + .into_iter() + .map(|ty| self.type_from_ast_type(ty)) + .collect(), + ), + ast::Type::Function(ast::FunctionType { args, ret }) => Type::Fun { + args: args + .into_iter() + .map(|t| self.type_from_ast_type(t)) + .collect(), + ret: Box::new(self.type_from_ast_type(*ret)), + }, + ast::Type::Var(id) => Type::Univ({ + let opt_tv = { self.type_vars.borrow_mut().0.get_by_left(&id).copied() }; + opt_tv.unwrap_or_else(|| { + let tv = self.fresh_tv(); + self.type_vars + .borrow_mut() + .0 + .insert_no_overwrite(id, tv) + .unwrap(); + tv + }) + }), + } + } + + fn name_univ(&self, tv: TyVar) -> Ident<'static> { + let mut vars = self.type_vars.borrow_mut(); + vars.0 + .get_by_right(&tv) + .map(Ident::to_owned) + .unwrap_or_else(|| { + let name = loop { + let name = vars.1.make_name(); + if !vars.0.contains_left(&name) { + break name; + } + }; + vars.0.insert_no_overwrite(name.clone(), tv).unwrap(); + name + }) + } + + fn commit_instantiations(&mut self) -> HashMap<Ident<'ast>, Type> { + let mut res = HashMap::new(); + let mut ctx = mem::take(&mut self.ctx); + for (_, v) in ctx.iter_mut() { + if let Type::Univ(tv) = v { + let tv_name = self.name_univ(*tv); + if let Some(concrete) = self.instantiations.resolve(&tv_name) { + res.insert(tv_name, concrete.clone()); + *v = concrete.clone(); + } + } + } + self.ctx = ctx; + self.instantiations.pop(); + res + } + + fn types_match(&self, type_: &Type, ast_type: &ast::Type<'ast>) -> bool { + match (type_, ast_type) { + (Type::Univ(u), ast::Type::Var(v)) => { + Some(u) == self.type_vars.borrow().0.get_by_left(v) + } + (Type::Univ(_), _) => false, + (Type::Exist(_), _) => false, + (Type::Unit, ast::Type::Unit) => true, + (Type::Unit, _) => false, + (Type::Nullary(_), _) => todo!(), + (Type::Prim(pr), ty) => ast::Type::from(*pr) == *ty, + (Type::Tuple(members), ast::Type::Tuple(members2)) => members + .iter() + .zip(members2.iter()) + .all(|(t1, t2)| self.types_match(t1, t2)), + (Type::Tuple(members), _) => false, + (Type::Fun { args, ret }, ast::Type::Function(ft)) => { + args.len() == ft.args.len() + && args + .iter() + .zip(&ft.args) + .all(|(a1, a2)| self.types_match(a1, &a2)) + && self.types_match(&*ret, &*ft.ret) + } + (Type::Fun { .. }, _) => false, + } + } +} + +pub fn typecheck_expr(expr: ast::Expr) -> Result<hir::Expr<ast::Type>> { + let mut typechecker = Typechecker::new(); + let typechecked = typechecker.tc_expr(expr)?; + typechecker.finalize_expr(typechecked) +} + +pub fn typecheck_toplevel(decls: Vec<ast::Decl>) -> Result<Vec<hir::Decl<ast::Type>>> { + let mut typechecker = Typechecker::new(); + let mut res = Vec::with_capacity(decls.len()); + for decl in decls { + if let Some(hir_decl) = typechecker.tc_decl(decl)? { + let hir_decl = typechecker.finalize_decl(hir_decl)?; + res.push(hir_decl); + } + typechecker.ctx.clear(); + } + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! assert_type { + ($expr: expr, $type: expr) => { + use crate::parser::{expr, type_}; + let parsed_expr = test_parse!(expr, $expr); + let parsed_type = test_parse!(type_, $type); + let res = typecheck_expr(parsed_expr).unwrap_or_else(|e| panic!("{}", e)); + assert!( + res.type_().alpha_equiv(&parsed_type), + "{} inferred type {}, but expected {}", + $expr, + res.type_(), + $type + ); + }; + + (toplevel($program: expr), $($decl: ident => $type: expr),+ $(,)?) => {{ + use crate::parser::{toplevel, type_}; + let program = test_parse!(toplevel, $program); + let res = typecheck_toplevel(program).unwrap_or_else(|e| panic!("{}", e)); + $( + let parsed_type = test_parse!(type_, $type); + let ident = Ident::try_from(::std::stringify!($decl)).unwrap(); + let decl = res.iter().find(|decl| { + matches!(decl, crate::ast::hir::Decl::Fun { name, .. } if name == &ident) + }).unwrap_or_else(|| panic!("Could not find declaration for {}", ident)); + assert!( + decl.type_().unwrap().alpha_equiv(&parsed_type), + "inferred type {} for {}, but expected {}", + decl.type_().unwrap(), + ident, + $type + ); + )+ + }}; + } + + macro_rules! assert_type_error { + ($expr: expr) => { + use crate::parser::expr; + let parsed_expr = test_parse!(expr, $expr); + let res = typecheck_expr(parsed_expr); + assert!( + res.is_err(), + "Expected type error, but got type: {}", + res.unwrap().type_() + ); + }; + } + + #[test] + fn literal_int() { + assert_type!("1", "int"); + } + + #[test] + fn conditional() { + assert_type!("if 1 == 2 then 3 else 4", "int"); + } + + #[test] + #[ignore] + fn add_bools() { + assert_type_error!("true + false"); + } + + #[test] + fn call_generic_function() { + assert_type!("(fn x = x) 1", "int"); + } + + #[test] + fn call_let_bound_generic() { + assert_type!("let id = fn x = x in id 1", "int"); + } + + #[test] + fn universal_ascripted_let() { + assert_type!("let id: fn a -> a = fn x = x in id 1", "int"); + } + + #[test] + fn call_generic_function_toplevel() { + assert_type!( + toplevel( + "ty id : fn a -> a + fn id x = x + + fn main = id 0" + ), + main => "fn -> int", + id => "fn a -> a", + ); + } + + #[test] + #[ignore] + fn let_generalization() { + assert_type!("let id = fn x = x in if id true then id 1 else 2", "int"); + } + + #[test] + fn concrete_function() { + assert_type!("fn x = x + 1", "fn int -> int"); + } + + #[test] + fn arg_ascriptions() { + assert_type!("fn (x: int) = x", "fn int -> int"); + } + + #[test] + fn call_concrete_function() { + assert_type!("(fn x = x + 1) 2", "int"); + } + + #[test] + fn conditional_non_bool() { + assert_type_error!("if 3 then true else false"); + } + + #[test] + fn let_int() { + assert_type!("let x = 1 in x", "int"); + } +} diff --git a/users/grfn/achilles/tests/compile.rs b/users/grfn/achilles/tests/compile.rs new file mode 100644 index 000000000000..0f1086bfd8e1 --- /dev/null +++ b/users/grfn/achilles/tests/compile.rs @@ -0,0 +1,79 @@ +use std::process::Command; + +use crate_root::root; + +struct Fixture { + name: &'static str, + exit_code: i32, + expected_output: &'static str, +} + +const FIXTURES: &[Fixture] = &[ + Fixture { + name: "simple", + exit_code: 5, + expected_output: "", + }, + Fixture { + name: "functions", + exit_code: 9, + expected_output: "", + }, + Fixture { + name: "externs", + exit_code: 0, + expected_output: "foobar\n", + }, + Fixture { + name: "units", + exit_code: 0, + expected_output: "hi\n", + }, +]; + +#[test] +fn compile_and_run_files() { + let ach = root().unwrap().join("ach"); + + println!("Running: `make clean`"); + assert!( + Command::new("make") + .arg("clean") + .current_dir(&ach) + .spawn() + .unwrap() + .wait() + .unwrap() + .success(), + "make clean failed" + ); + + for Fixture { + name, + exit_code, + expected_output, + } in FIXTURES + { + println!(">>> Testing: {}", name); + + println!(" Running: `make {}`", name); + assert!( + Command::new("make") + .arg(name) + .current_dir(&ach) + .spawn() + .unwrap() + .wait() + .unwrap() + .success(), + "make failed" + ); + + let out_path = ach.join(name); + println!(" Running: `{}`", out_path.to_str().unwrap()); + let output = Command::new(out_path).output().unwrap(); + assert_eq!(output.status.code().unwrap(), *exit_code,); + assert_eq!(output.stdout, expected_output.as_bytes()); + println!(" OK"); + } +} diff --git a/users/grfn/emacs.d/+bindings.el b/users/grfn/emacs.d/+bindings.el new file mode 100644 index 000000000000..508879e417ec --- /dev/null +++ b/users/grfn/emacs.d/+bindings.el @@ -0,0 +1,1429 @@ +;; -*- lexical-binding: t; -*- + +(load! "utils") +(require 'f) +(require 'predd) + +(undefine-key! :keymaps 'doom-leader-map "/") + +(defmacro find-file-in! (path &optional project-p) + "Returns an interactive function for searching files." + `(lambda () (interactive) + (let ((default-directory ,path)) + (call-interactively + ',(command-remapping + (if project-p + #'projectile-find-file + #'find-file)))))) + +(defun dired-mode-p () (eq 'dired-mode major-mode)) + +(defun grfn/dired-minus () + (interactive) + (if (dired-mode-p) + (dired-up-directory) + (when buffer-file-name + (-> (buffer-file-name) + (f-dirname) + (dired))))) + +(defmacro define-move-and-insert + (name &rest body) + `(defun ,name (count &optional vcount skip-empty-lines) + ;; Following interactive form taken from the source for `evil-insert' + (interactive + (list (prefix-numeric-value current-prefix-arg) + (and (evil-visual-state-p) + (memq (evil-visual-type) '(line block)) + (save-excursion + (let ((m (mark))) + ;; go to upper-left corner temporarily so + ;; `count-lines' yields accurate results + (evil-visual-rotate 'upper-left) + (prog1 (count-lines evil-visual-beginning evil-visual-end) + (set-mark m))))) + (evil-visual-state-p))) + (atomic-change-group + ,@body + (evil-insert count vcount skip-empty-lines)))) + +(define-move-and-insert grfn/insert-at-sexp-end + (when (not (equal (get-char) "(")) + (backward-up-list)) + (forward-sexp) + (backward-char)) + +(define-move-and-insert grfn/insert-at-sexp-start + (backward-up-list) + (forward-char)) + +(define-move-and-insert grfn/insert-at-form-start + (backward-sexp) + (backward-char) + (insert " ")) + +(define-move-and-insert grfn/insert-at-form-end + (forward-sexp) + (insert " ")) + +(load! "splitjoin") + +(defun +hlissner/install-snippets () + "Install my snippets from https://github.com/hlissner/emacs-snippets into +private/hlissner/snippets." + (interactive) + (doom-fetch :github "hlissner/emacs-snippets" + (expand-file-name "snippets" (doom-module-path :private 'hlissner)))) + +(defun +hlissner/yank-buffer-filename () + "Copy the current buffer's path to the kill ring." + (interactive) + (if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory)))) + (message (kill-new (abbreviate-file-name filename))) + (error "Couldn't find filename in current buffer"))) + +(defmacro +def-finder! (name dir) + "Define a pair of find-file and browse functions." + `(progn + (defun ,(intern (format "+find-in-%s" name)) () + (interactive) + (let ((default-directory ,dir) + projectile-project-name + projectile-require-project-root + projectile-cached-buffer-file-name + projectile-cached-project-root) + (call-interactively #'projectile-find-file))) + (defun ,(intern (format "+hlissner/browse-%s" name)) () + (interactive) + (let ((default-directory ,dir)) + (call-interactively (command-remapping #'find-file)))))) + +(+def-finder! templates +file-templates-dir) +(+def-finder! snippets +grfn-snippets-dir) +(+def-finder! dotfiles (expand-file-name ".dotfiles" "~")) +(+def-finder! doomd (expand-file-name ".doom.d" "~")) +(+def-finder! notes +org-dir) +(+def-finder! home-config (expand-file-name "code/system/home" "~")) +(+def-finder! system-config (expand-file-name "code/system/system" "~")) + +(defun +grfn/paxedit-kill (&optional n) + (interactive "p") + (or (paxedit-comment-kill) + (when (paxedit-symbol-cursor-within?) + (paxedit-symbol-kill)) + (paxedit-implicit-sexp-kill n) + (paxedit-sexp-kill n) + (message paxedit-message-kill))) + +;;; + +(evil-set-command-property 'flycheck-next-error :repeat nil) +(evil-set-command-property 'flycheck-prev-error :repeat nil) + +;;; + +(map! + [remap evil-jump-to-tag] #'projectile-find-tag + [remap find-tag] #'projectile-find-tag + ;; ensure there are no conflicts + :nmvo doom-leader-key nil + :nmvo doom-localleader-key nil) + +(undefine-key! :keymaps 'doom-leader-map "/") + +(map! + ;; --- Global keybindings --------------------------- + ;; Make M-x available everywhere + :gnvime "M-x" #'execute-extended-command + :gnvime "A-x" #'execute-extended-command + ;; Emacs debug utilities + :gnvime "M-;" #'eval-expression + :gnvime "M-:" #'doom/open-scratch-buffer + ;; Text-scaling + "M-+" (ฮป! (text-scale-set 0)) + "M-=" #'text-scale-increase + "M--" #'text-scale-decrease + ;; Simple window navigation/manipulation + "C-`" #'doom/popup-toggle + "C-~" #'doom/popup-raise + "M-t" #'+workspace/new + "M-T" #'+workspace/display + "M-w" #'delete-window + "M-W" #'+workspace/close-workspace-or-frame + "M-n" #'evil-buffer-new + "M-N" #'make-frame + "M-1" (ฮป! (+workspace/switch-to 0)) + "M-2" (ฮป! (+workspace/switch-to 1)) + "M-3" (ฮป! (+workspace/switch-to 2)) + "M-4" (ฮป! (+workspace/switch-to 3)) + "M-5" (ฮป! (+workspace/switch-to 4)) + "M-6" (ฮป! (+workspace/switch-to 5)) + "M-7" (ฮป! (+workspace/switch-to 6)) + "M-8" (ฮป! (+workspace/switch-to 7)) + "M-9" (ฮป! (+workspace/switch-to 8)) + "M-0" #'+workspace/switch-to-last + ;; Other sensible, textmate-esque global bindings + :ne "M-r" #'+eval/buffer + :ne "M-R" #'+eval/region-and-replace + :ne "M-b" #'+eval/build + :ne "M-a" #'mark-whole-buffer + :ne "M-c" #'evil-yank + :ne "M-q" (if (daemonp) #'delete-frame #'save-buffers-kill-emacs) + :ne "M-f" #'swiper + :ne "C-M-f" #'doom/toggle-fullscreen + :n "M-s" #'save-buffer + :m "A-j" #'+hlissner:multi-next-line + :m "A-k" #'+hlissner:multi-previous-line + :nv "C-SPC" #'+evil:fold-toggle + :gnvimer "M-v" #'clipboard-yank + ;; Easier window navigation + :en "C-h" #'evil-window-left + :en "C-j" #'evil-window-down + :en "C-k" #'evil-window-up + :en "C-l" #'evil-window-right + :n "U" #'undo-tree-visualize + + "C-x p" #'doom/other-popup + + :n "K" #'+lookup/documentation + :n "g d" #'+lookup/definition + + + ;; --- <leader> ------------------------------------- + (:leader + :desc "Ex command" :nv ";" #'evil-ex + :desc "M-x" :nv ":" #'execute-extended-command + :desc "Pop up scratch buffer" :nv "x" #'doom/open-scratch-buffer + :desc "Org Capture" :nv "X" #'org-capture + :desc "Org Capture" :nv "a" #'org-capture + + ;; Most commonly used + :desc "Find file in project" :n "SPC" #'projectile-find-file + :desc "Switch workspace buffer" :n "," #'persp-switch-to-buffer + :desc "Switch buffer" :n "<" #'switch-to-buffer + :desc "Browse files" :n "." #'find-file + :desc "Toggle last popup" :n "~" #'doom/popup-toggle + :desc "Eval expression" :n "`" #'eval-expression + :desc "Blink cursor line" :n "DEL" #'+doom/blink-cursor + :desc "Jump to bookmark" :n "RET" #'bookmark-jump + + ;; C-u is used by evil + :desc "Universal argument" :n "u" #'universal-argument + :desc "window" :n "w" evil-window-map + + (:desc "previous..." :prefix "[" + :desc "Text size" :nv "[" #'text-scale-decrease + :desc "Buffer" :nv "b" #'doom/previous-buffer + :desc "Diff Hunk" :nv "d" #'git-gutter:previous-hunk + :desc "Todo" :nv "t" #'hl-todo-previous + :desc "Error" :nv "e" #'flycheck-previous-error + :desc "Workspace" :nv "w" #'+workspace/switch-left + :desc "Smart jump" :nv "h" #'smart-backward + :desc "Spelling error" :nv "s" #'evil-prev-flyspell-error + :desc "Spelling correction" :n "S" #'flyspell-correct-previous-word-generic + :desc "Git conflict" :n "n" #'smerge-prev) + + (:desc "next..." :prefix "]" + :desc "Text size" :nv "]" #'text-scale-increase + :desc "Buffer" :nv "b" #'doom/next-buffer + :desc "Diff Hunk" :nv "d" #'git-gutter:next-hunk + :desc "Todo" :nv "t" #'hl-todo-next + :desc "Error" :nv "e" #'flycheck-next-error + :desc "Workspace" :nv "w" #'+workspace/switch-right + :desc "Smart jump" :nv "l" #'smart-forward + :desc "Spelling error" :nv "s" #'evil-next-flyspell-error + :desc "Spelling correction" :n "S" #'flyspell-correct-word-generic + :desc "Git conflict" :n "n" #'smerge-next) + + (:desc "search" :prefix "/" + :desc "Swiper" :nv "/" #'swiper + :desc "Imenu" :nv "i" #'imenu + :desc "Imenu across buffers" :nv "I" #'imenu-anywhere + :desc "Online providers" :nv "o" #'+lookup/online-select) + + (:desc "workspace" :prefix "TAB" + :desc "Display tab bar" :n "TAB" #'+workspace/display + :desc "New workspace" :n "n" #'+workspace/new + :desc "Load workspace from file" :n "l" #'+workspace/load + :desc "Load last session" :n "L" (ฮป! (+workspace/load-session)) + :desc "Save workspace to file" :n "s" #'+workspace/save + :desc "Autosave current session" :n "S" #'+workspace/save-session + :desc "Switch workspace" :n "." #'+workspace/switch-to + :desc "Kill all buffers" :n "x" #'doom/kill-all-buffers + :desc "Delete session" :n "X" #'+workspace/kill-session + :desc "Delete this workspace" :n "d" #'+workspace/delete + :desc "Load session" :n "L" #'+workspace/load-session + :desc "Next workspace" :n "]" #'+workspace/switch-right + :desc "Previous workspace" :n "[" #'+workspace/switch-left + :desc "Switch to 1st workspace" :n "1" (ฮป! (+workspace/switch-to 0)) + :desc "Switch to 2nd workspace" :n "2" (ฮป! (+workspace/switch-to 1)) + :desc "Switch to 3rd workspace" :n "3" (ฮป! (+workspace/switch-to 2)) + :desc "Switch to 4th workspace" :n "4" (ฮป! (+workspace/switch-to 3)) + :desc "Switch to 5th workspace" :n "5" (ฮป! (+workspace/switch-to 4)) + :desc "Switch to 6th workspace" :n "6" (ฮป! (+workspace/switch-to 5)) + :desc "Switch to 7th workspace" :n "7" (ฮป! (+workspace/switch-to 6)) + :desc "Switch to 8th workspace" :n "8" (ฮป! (+workspace/switch-to 7)) + :desc "Switch to 9th workspace" :n "9" (ฮป! (+workspace/switch-to 8)) + :desc "Switch to last workspace" :n "0" #'+workspace/switch-to-last) + + (:desc "buffer" :prefix "b" + :desc "New empty buffer" :n "n" #'evil-buffer-new + :desc "Switch workspace buffer" :n "b" #'switch-to-buffer + :desc "Switch buffer" :n "B" #'switch-to-buffer + :desc "Kill buffer" :n "k" #'doom/kill-this-buffer + :desc "Kill other buffers" :n "o" #'doom/kill-other-buffers + :desc "Save buffer" :n "s" #'save-buffer + :desc "Pop scratch buffer" :n "x" #'doom/open-scratch-buffer + :desc "Bury buffer" :n "z" #'bury-buffer + :desc "Next buffer" :n "]" #'doom/next-buffer + :desc "Previous buffer" :n "[" #'doom/previous-buffer + :desc "Sudo edit this file" :n "S" #'doom/sudo-this-file) + + (:desc "code" :prefix "c" + :desc "List errors" :n "x" #'flycheck-list-errors + :desc "Evaluate buffer/region" :n "e" #'+eval/buffer + :v "e" #'+eval/region + :desc "Evaluate & replace region" :nv "E" #'+eval:replace-region + :desc "Build tasks" :nv "b" #'+eval/build + :desc "Jump to definition" :n "d" #'+lookup/definition + :desc "Jump to references" :n "D" #'+lookup/references + :desc "Open REPL" :n "r" #'+eval/open-repl + :v "r" #'+eval:repl) + + (:desc "file" :prefix "f" + :desc "Find file" :n "." #'find-file + :desc "Sudo find file" :n ">" #'doom/sudo-find-file + :desc "Find file in project" :n "/" #'projectile-find-file + :desc "Find file from here" :n "?" #'counsel-file-jump + :desc "Find other file" :n "a" #'projectile-find-other-file + :desc "Open project editorconfig" :n "c" #'editorconfig-find-current-editorconfig + :desc "Find file in dotfiles" :n "d" #'+find-in-dotfiles + :desc "Find file in system config" :n "s" #'+find-in-system-config + :desc "Find file in home config" :n "h" #'+find-in-home-config + :desc "Browse dotfiles" :n "D" #'+hlissner/browse-dotfiles + :desc "Find file in emacs.d" :n "e" #'+find-in-doomd + :desc "Browse emacs.d" :n "E" #'+hlissner/browse-doomd + :desc "Recent files" :n "r" #'recentf-open-files + :desc "Recent project files" :n "R" #'projectile-recentf + :desc "Yank filename" :n "y" #'+hlissner/yank-buffer-filename) + + (:desc "git" :prefix "g" + :desc "Git status" :n "S" #'magit-status + :desc "Git blame" :n "b" #'magit-blame + :desc "Git time machine" :n "t" #'git-timemachine-toggle + :desc "Git stage hunk" :n "s" #'git-gutter:stage-hunk + :desc "Git revert hunk" :n "r" #'git-gutter:revert-hunk + :desc "Git revert buffer" :n "R" #'vc-revert + ;; :desc "List gists" :n "g" #'+gist:list + :desc "Git grep" :n "g" #'counsel-projectile-rg + :desc "Checkout Branch" :n "c" #'counsel-git-checkout + :desc "Next hunk" :nv "]" #'git-gutter:next-hunk + :desc "Previous hunk" :nv "[" #'git-gutter:previous-hunk + + (:desc "smerge" :prefix "m" + :desc "Keep Current" :n "SPC" #'smerge-keep-current + :desc "Keep All" :n "a" #'smerge-keep-all + :desc "Keep Upper" :n "u" #'smerge-keep-upper + :desc "Keep Lower" :n "l" #'smerge-keep-lower)) + + (:desc "help" :prefix "h" + :n "h" help-map + :desc "Apropos" :n "a" #'apropos + :desc "Reload theme" :n "R" #'doom//reload-theme + :desc "Find library" :n "l" #'find-library + :desc "Toggle Emacs log" :n "m" #'doom/popup-toggle-messages + :desc "Command log" :n "L" #'global-command-log-mode + :desc "Describe function" :n "f" #'describe-function + :desc "Describe key" :n "k" #'describe-key + :desc "Describe char" :n "c" #'describe-char + :desc "Describe mode" :n "M" #'describe-mode + :desc "Describe variable" :n "v" #'describe-variable + :desc "Describe face" :n "F" #'describe-face + :desc "Describe DOOM setting" :n "s" #'doom/describe-setting + :desc "Describe DOOM module" :n "d" #'doom/describe-module + :desc "Find definition" :n "." #'+lookup/definition + :desc "Find references" :n "/" #'+lookup/references + :desc "Find documentation" :n "h" #'+lookup/documentation + :desc "What face" :n "'" #'doom/what-face + :desc "What minor modes" :n ";" #'doom/what-minor-mode + :desc "Info" :n "i" #'info + :desc "Toggle profiler" :n "p" #'doom/toggle-profiler) + + (:desc "insert" :prefix "i" + :desc "From kill-ring" :nv "y" #'counsel-yank-pop + :desc "From snippet" :nv "s" #'yas-insert-snippet) + + (:desc "notes" :prefix "n" + :desc "Agenda" :n "a" #'org-agenda + :desc "Find file in notes" :n "n" #'+find-in-notes + :desc "Store link" :n "l" #'org-store-link + :desc "Browse notes" :n "N" #'+hlissner/browse-notes + :desc "Org capture" :n "x" #'+org-capture/open + :desc "Create clubhouse story" :n "c" #'org-tracker-create-issue + :desc "Archive subtree" :n "k" #'org-archive-subtree + :desc "Goto clocked-in note" :n "g" #'org-clock-goto + :desc "Clock Out" :n "o" #'org-clock-out) + + + (:desc "open" :prefix "o" + :desc "Default browser" :n "b" #'browse-url-of-file + :desc "Debugger" :n "d" #'+debug/open + :desc "Terminal in project" :n "T" #'+term/open-popup-in-project + + :desc "Slack IM" :n "i" #'slack-im-select + :desc "Slack Channel" :n "c" #'slack-channel-select + :desc "Slack Group" :n "g" #'slack-group-select + :desc "Slack Unreads" :n "u" #'slack-select-unread-rooms + :desc "Slack Threads" :n "r" #'slack-all-threads + + :desc "Email" :n "m" #'notmuch-tree-jump-search + + (:desc "ERC" :prefix "e" + :desc "Channel" :n "c" #'erc-switch-to-buffer) + + ;; applications + :desc "APP: elfeed" :n "E" #'=rss + :desc "APP: twitter" :n "T" #'=twitter + + (:desc "spotify" :prefix "s" + :desc "Search track" :n "t" #'counsel-spotify-search-track + :desc "Search album" :n "a" #'counsel-spotify-search-album + :desc "Search artist" :n "A" #'counsel-spotify-search-artist) + + ;; macos + (:when IS-MAC + :desc "Reveal in Finder" :n "o" #'+macos/reveal-in-finder + :desc "Reveal project in Finder" :n "O" #'+macos/reveal-project-in-finder + :desc "Send to Transmit" :n "u" #'+macos/send-to-transmit + :desc "Send project to Transmit" :n "U" #'+macos/send-project-to-transmit + :desc "Send to Launchbar" :n "l" #'+macos/send-to-launchbar + :desc "Send project to Launchbar" :n "L" #'+macos/send-project-to-launchbar)) + + (:desc "Email" :prefix "M" + :desc "Compose" :n "m" #'+notmuch/compose) + + (:desc "project" :prefix "p" + :desc "Browse project" :n "." (find-file-in! (doom-project-root)) + :desc "Find file in project" :n "/" #'projectile-find-file + :desc "Run cmd in project root" :nv "!" #'projectile-run-shell-command-in-root + :desc "Switch project" :n "p" #'projectile-switch-project + :desc "Recent project files" :n "r" #'projectile-recentf + :desc "List project tasks" :n "t" #'+ivy/tasks + :desc "Pop term in project" :n "o" #'+term/open-popup-in-project + :desc "Invalidate cache" :n "x" #'projectile-invalidate-cache) + + (:desc "quit" :prefix "q" + :desc "Quit" :n "q" #'evil-save-and-quit + :desc "Quit (forget session)" :n "Q" #'+workspace/kill-session-and-quit) + + (:desc "remote" :prefix "r" + :desc "Upload local" :n "u" #'+upload/local + :desc "Upload local (force)" :n "U" (ฮป! (+upload/local t)) + :desc "Download remote" :n "d" #'+upload/remote-download + :desc "Diff local & remote" :n "D" #'+upload/diff + :desc "Browse remote files" :n "." #'+upload/browse + :desc "Detect remote changes" :n ">" #'+upload/check-remote) + + (:desc "snippets" :prefix "s" + :desc "New snippet" :n "n" #'yas-new-snippet + :desc "Insert snippet" :nv "i" #'yas-insert-snippet + :desc "Find snippet for mode" :n "s" #'yas-visit-snippet-file + :desc "Find snippet" :n "S" #'+find-in-snippets) + + (:desc "toggle" :prefix "t" + :desc "Flyspell" :n "s" #'flyspell-mode + :desc "Flycheck" :n "f" #'flycheck-mode + :desc "Line numbers" :n "l" #'doom/toggle-line-numbers + :desc "Fullscreen" :n "f" #'doom/toggle-fullscreen + :desc "Indent guides" :n "i" #'highlight-indentation-mode + :desc "Indent guides (column)" :n "I" #'highlight-indentation-current-column-mode + :desc "Impatient mode" :n "h" #'+impatient-mode/toggle + :desc "Big mode" :n "b" #'doom-big-font-mode + :desc "Evil goggles" :n "g" #'+evil-goggles/toggle)) + + + ;; --- vim-vinegar + :n "-" #'grfn/dired-minus + (:after dired-mode + (:map dired-mode-map + "-" #'grfn/dired-minus)) + + (:map smartparens-mode-map + :n "g o" #'sp-raise-sexp) + + ;; --- vim-sexp-mappings-for-regular-people + (:after paxedit + (:map paxedit-mode-map + :i ";" #'paxedit-insert-semicolon + :i "(" #'paxedit-open-round + :i "[" #'paxedit-open-bracket + :i "{" #'paxedit-open-curly + :n [remap evil-yank-line] #'paxedit-copy + :n [remap evil-delete-line] #'+grfn/paxedit-kill + :n "g o" #'paxedit-sexp-raise + :n [remap evil-join-whitespace] #'paxedit-compress + :n "g S" #'paxedit-format-1 + :n "g k" #'paxedit-backward-up + :n "g j" #'paxedit-backward-end)) + + ;; --- vim-splitjoin + :n [remap evil-join-whitespace] #'+splitjoin/join + :n "gS" #'+splitjoin/split + + ;; --- Personal vim-esque bindings ------------------ + :n "zx" #'doom/kill-this-buffer + :n "ZX" #'bury-buffer + :n "]b" #'doom/next-buffer + :n "[b" #'doom/previous-buffer + :n "]w" #'+workspace/switch-right + :n "[w" #'+workspace/switch-left + :m "gt" #'+workspace/switch-right + :m "gT" #'+workspace/switch-left + :m "gd" #'+lookup/definition + :m "gD" #'+lookup/references + :m "K" #'+lookup/documentation + :n "gp" #'+evil/reselect-paste + :n "gr" #'+eval:region + :n "gR" #'+eval/buffer + :v "gR" #'+eval:replace-region + :v "@" #'+evil:macro-on-all-lines + :n "g@" #'+evil:macro-on-all-lines + ;; repeat in visual mode (FIXME buggy) + :v "." #'evil-repeat + ;; don't leave visual mode after shifting + ;; :v "<" #'+evil/visual-dedent ; vnoremap < <gv + ;; :v ">" #'+evil/visual-indent ; vnoremap > >gv + ;; paste from recent yank register (which isn't overwritten) + :v "C-p" "\"0p" + + (:map evil-window-map ; prefix "C-w" + ;; Navigation + "C-h" #'evil-window-left + "C-j" #'evil-window-down + "C-k" #'evil-window-up + "C-l" #'evil-window-right + "C-w" #'ace-window + ;; Swapping windows + "H" #'+evil/window-move-left + "J" #'+evil/window-move-down + "K" #'+evil/window-move-up + "L" #'+evil/window-move-right + "C-S-w" #'ace-swap-window + ;; Window undo/redo + "u" #'winner-undo + "C-u" #'winner-undo + "C-r" #'winner-redo + "o" #'doom/window-enlargen + ;; Delete window + "c" #'+workspace/close-window-or-workspace + "C-C" #'ace-delete-window + ;; Popups + "p" #'doom/popup-toggle + "m" #'doom/popup-toggle-messages + "P" #'doom/popup-close-all) + + + ;; --- Plugin bindings ------------------------------ + ;; auto-yasnippet + :i [C-tab] #'aya-expand + :nv [C-tab] #'aya-create + + ;; company-mode (vim-like omnicompletion) + :i "C-SPC" #'+company/complete + (:prefix "C-x" + :i "C-l" #'+company/whole-lines + :i "C-k" #'+company/dict-or-keywords + :i "C-f" #'company-files + :i "C-]" #'company-etags + :i "s" #'company-ispell + :i "C-s" #'company-yasnippet + :i "C-o" #'company-capf + :i "C-n" #'company-dabbrev-code + :i "C-p" #'+company/dabbrev-code-previous) + (:after company + (:map company-active-map + ;; Don't interfere with `evil-delete-backward-word' in insert mode + "C-w" nil + "C-o" #'company-search-kill-others + "C-n" #'company-select-next + "C-p" #'company-select-previous + "C-h" #'company-quickhelp-manual-begin + "C-S-h" #'company-show-doc-buffer + "C-S-s" #'company-search-candidates + "C-s" #'company-filter-candidates + "C-SPC" #'company-complete-common + "C-h" #'company-quickhelp-manual-begin + [tab] #'company-complete-common-or-cycle + [backtab] #'company-select-previous + [escape] (ฮป! (company-abort) (evil-normal-state 1))) + ;; Automatically applies to `company-filter-map' + (:map company-search-map + "C-n" #'company-search-repeat-forward + "C-p" #'company-search-repeat-backward + "C-s" (ฮป! (company-search-abort) (company-filter-candidates)) + [escape] #'company-search-abort)) + + ;; counsel +; (:after counsel +; (:map counsel-ag-map +; [backtab] #'+ivy/wgrep-occur ; search/replace on results +; "C-SPC" #'ivy-call-and-recenter ; preview)) + + ;; evil-commentary + ;; :n "gc" #'evil-commentary + + ;; evil-exchange + :n "gx" #'evil-exchange + + ;; evil-magit + (:after evil-magit + :map (magit-status-mode-map magit-revision-mode-map) + :n "C-j" nil + :n "C-k" nil) + + ;; Smerge + :n "]n" #'smerge-next + :n "[n" #'smerge-prev + + ;; evil-mc + (:prefix "gz" + :nv "m" #'evil-mc-make-all-cursors + :nv "u" #'evil-mc-undo-all-cursors + :nv "z" #'+evil/mc-make-cursor-here + :nv "t" #'+evil/mc-toggle-cursors + :nv "n" #'evil-mc-make-and-goto-next-cursor + :nv "p" #'evil-mc-make-and-goto-prev-cursor + :nv "N" #'evil-mc-make-and-goto-last-cursor + :nv "P" #'evil-mc-make-and-goto-first-cursor + :nv "d" #'evil-mc-make-and-goto-next-match + :nv "D" #'evil-mc-make-and-goto-prev-match) + (:after evil-mc + :map evil-mc-key-map + :nv "C-n" #'evil-mc-make-and-goto-next-cursor + :nv "C-N" #'evil-mc-make-and-goto-last-cursor + :nv "C-p" #'evil-mc-make-and-goto-prev-cursor + :nv "C-P" #'evil-mc-make-and-goto-first-cursor) + + ;; evil-multiedit + :v "R" #'evil-multiedit-match-all + :n "M-d" #'evil-multiedit-match-symbol-and-next + :n "M-D" #'evil-multiedit-match-symbol-and-prev + :v "M-d" #'evil-multiedit-match-and-next + :v "M-D" #'evil-multiedit-match-and-prev + :nv "C-M-d" #'evil-multiedit-restore + (:after evil-multiedit + (:map evil-multiedit-state-map + "M-d" #'evil-multiedit-match-and-next + "M-D" #'evil-multiedit-match-and-prev + "RET" #'evil-multiedit-toggle-or-restrict-region) + (:map (evil-multiedit-state-map evil-multiedit-insert-state-map) + "C-n" #'evil-multiedit-next + "C-p" #'evil-multiedit-prev)) + + ;; evil-snipe + (:after evil-snipe + ;; Binding to switch to evil-easymotion/avy after a snipe + :map evil-snipe-parent-transient-map + "C-;" (ฮป! (require 'evil-easymotion) + (call-interactively + (evilem-create #'evil-snipe-repeat + :bind ((evil-snipe-scope 'whole-buffer) + (evil-snipe-enable-highlight) + (evil-snipe-enable-incremental-highlight)))))) + + ;; evil-surround + :v "S" #'evil-surround-region + :o "s" #'evil-surround-edit + :o "S" #'evil-Surround-edit + + ;; expand-region + :v "v" #'er/expand-region + :v "V" #'er/contract-region + + ;; flycheck + :m "]e" #'flycheck-next-error + :m "[e" #'flycheck-previous-error + (:after flycheck + :map flycheck-error-list-mode-map + :n "C-n" #'flycheck-error-list-next-error + :n "C-p" #'flycheck-error-list-previous-error + :n "j" #'flycheck-error-list-next-error + :n "k" #'flycheck-error-list-previous-error + :n "RET" #'flycheck-error-list-goto-error) + + ;; flyspell + :m "]S" #'flyspell-correct-word-generic + :m "[S" #'flyspell-correct-previous-word-generic + + ;; git-gutter + :m "]d" #'git-gutter:next-hunk + :m "[d" #'git-gutter:previous-hunk + + ;; git-timemachine + (:after git-timemachine + (:map git-timemachine-mode-map + :n "C-p" #'git-timemachine-show-previous-revision + :n "C-n" #'git-timemachine-show-next-revision + :n "[[" #'git-timemachine-show-previous-revision + :n "]]" #'git-timemachine-show-next-revision + :n "q" #'git-timemachine-quit + :n "gb" #'git-timemachine-blame)) + + ;; gist + (:after gist + :map gist-list-menu-mode-map + :n "RET" #'+gist/open-current + :n "b" #'gist-browse-current-url + :n "c" #'gist-add-buffer + :n "d" #'gist-kill-current + :n "f" #'gist-fork + :n "q" #'quit-window + :n "r" #'gist-list-reload + :n "s" #'gist-star + :n "S" #'gist-unstar + :n "y" #'gist-print-current-url) + + ;; helm + (:after helm + (:map helm-map + "ESC" nil + "C-S-n" #'helm-next-source + "C-S-p" #'helm-previous-source + "C-u" #'helm-delete-minibuffer-contents + "C-w" #'backward-kill-word + "C-r" #'evil-paste-from-register ; Evil registers in helm! Glorious! + "C-b" #'backward-word + [left] #'backward-char + [right] #'forward-char + [escape] #'helm-keyboard-quit + [tab] #'helm-execute-persistent-action) + + (:after helm-files + (:map helm-generic-files-map + :e "ESC" #'helm-keyboard-quit) + (:map helm-find-files-map + "C-w" #'helm-find-files-up-one-level + "TAB" #'helm-execute-persistent-action)) + + (:after helm-ag + (:map helm-ag-map + "<backtab>" #'helm-ag-edit))) + + ;; hl-todo + :m "]t" #'hl-todo-next + :m "[t" #'hl-todo-previous + + ;; ivy + (:after ivy + :map ivy-minibuffer-map + [escape] #'keyboard-escape-quit + "C-SPC" #'ivy-call-and-recenter + "TAB" #'ivy-partial + "M-v" #'yank + "M-z" #'undo + "C-r" #'evil-paste-from-register + "C-k" #'ivy-previous-line + "C-j" #'ivy-next-line + "C-l" #'ivy-alt-done + "C-w" #'ivy-backward-kill-word + "C-u" #'ivy-kill-line + "C-b" #'backward-word + "C-f" #'forward-word) + + ;; neotree + (:after neotree + :map neotree-mode-map + :n "g" nil + :n [tab] #'neotree-quick-look + :n "RET" #'neotree-enter + :n [backspace] #'evil-window-prev + :n "c" #'neotree-create-node + :n "r" #'neotree-rename-node + :n "d" #'neotree-delete-node + :n "j" #'neotree-next-line + :n "k" #'neotree-previous-line + :n "n" #'neotree-next-line + :n "p" #'neotree-previous-line + :n "h" #'+neotree/collapse-or-up + :n "l" #'+neotree/expand-or-open + :n "J" #'neotree-select-next-sibling-node + :n "K" #'neotree-select-previous-sibling-node + :n "H" #'neotree-select-up-node + :n "L" #'neotree-select-down-node + :n "G" #'evil-goto-line + :n "gg" #'evil-goto-first-line + :n "v" #'neotree-enter-vertical-split + :n "s" #'neotree-enter-horizontal-split + :n "q" #'neotree-hide + :n "R" #'neotree-refresh) + + ;; realgud + (:after realgud + :map realgud:shortkey-mode-map + :n "j" #'evil-next-line + :n "k" #'evil-previous-line + :n "h" #'evil-backward-char + :n "l" #'evil-forward-char + :m "n" #'realgud:cmd-next + :m "b" #'realgud:cmd-break + :m "B" #'realgud:cmd-clear + :n "c" #'realgud:cmd-continue) + + ;; rotate-text + :n "gs" #'rotate-text + + ;; smart-forward + :m "g]" #'smart-forward + :m "g[" #'smart-backward + + ;; undo-tree -- undo/redo for visual regions + :v "C-u" #'undo-tree-undo + :v "C-r" #'undo-tree-redo + + ;; yasnippet + (:after yasnippet + (:map yas-keymap + "C-e" #'+snippets/goto-end-of-field + "C-a" #'+snippets/goto-start-of-field + "<M-right>" #'+snippets/goto-end-of-field + "<M-left>" #'+snippets/goto-start-of-field + "<M-backspace>" #'+snippets/delete-to-start-of-field + [escape] #'evil-normal-state + [backspace] #'+snippets/delete-backward-char + [delete] #'+snippets/delete-forward-char-or-field) + (:map yas-minor-mode-map + :i "<tab>" yas-maybe-expand + :v "<tab>" #'+snippets/expand-on-region)) + + + ;; --- Major mode bindings -------------------------- + + ;; Markdown + (:after markdown-mode + (:map markdown-mode-map + ;; fix conflicts with private bindings + "<backspace>" nil + "<M-left>" nil + "<M-right>" nil)) + + ;; Rust + (:after rust + (:map rust-mode-map + "K" #'racer-describe + "g RET" #'cargo-process-test)) + + ;; Elixir + (:after alchemist + (:map elixir-mode-map + :n "K" #'alchemist-help-search-at-point + :n "g RET" #'alchemist-project-run-tests-for-current-file + :n "g \\" #'alchemist-mix-test-at-point + :n "g SPC" #'alchemist-mix-compile)) + + ;; Haskell + (:after haskell-mode + (:map haskell-mode-map + ;; :n "K" #'intero-info + :n "K" #'lsp-describe-thing-at-point + ;; :n "g d" #'lsp-ui-peek-find-definitions + :n "g d" #'lsp-ui-peek-find-definitions + :n "g R" #'lsp-find-references + ;; :n "g SPC" #'intero-repl-load + ;; :n "g y" #'lsp-ui- + )) + + ;; Javascript + ;; (:after rjsx-mode + ;; (:map rjsx-mode-map + ;; :n "g d" #'flow-minor-jump-to-definition + ;; :n "K" #'flow-minor-type-at-pos)) + + (:after js2-mode + (:map js2-mode-map + :n "g d" #'flow-minor-jump-to-definition + :n "K" #'flow-minor-type-at-pos)) + + ;; Elisp + (:map emacs-lisp-mode-map + :n "g SPC" #'eval-buffer + :n "g RET" (ฮป! () (ert t))) + + + ;; --- Custom evil text-objects --------------------- + :textobj "a" #'evil-inner-arg #'evil-outer-arg + :textobj "B" #'evil-textobj-anyblock-inner-block #'evil-textobj-anyblock-a-block + :textobj "i" #'evil-indent-plus-i-indent #'evil-indent-plus-a-indent + :textobj "I" #'evil-indent-plus-i-indent-up #'evil-indent-plus-a-indent-up + :textobj "J" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down + + + ;; --- Built-in plugins ----------------------------- + (:after comint + ;; TAB auto-completion in term buffers + :map comint-mode-map [tab] #'company-complete) + + (:after debug + ;; For elisp debugging + :map debugger-mode-map + :n "RET" #'debug-help-follow + :n "e" #'debugger-eval-expression + :n "n" #'debugger-step-through + :n "c" #'debugger-continue) + + (:map help-mode-map + :n "[[" #'help-go-back + :n "]]" #'help-go-forward + :n "o" #'ace-link-help + :n "q" #'quit-window + :n "Q" #'+ivy-quit-and-resume) + + (:after vc-annotate + :map vc-annotate-mode-map + :n "q" #'kill-this-buffer + :n "d" #'vc-annotate-show-diff-revision-at-line + :n "D" #'vc-annotate-show-changeset-diff-revision-at-line + :n "SPC" #'vc-annotate-show-log-revision-at-line + :n "]]" #'vc-annotate-next-revision + :n "[[" #'vc-annotate-prev-revision + :n "TAB" #'vc-annotate-toggle-annotation-visibility + :n "RET" #'vc-annotate-find-revision-at-line)) + +;; evil-easymotion +(after! evil-easymotion + (let ((prefix (concat doom-leader-key " /"))) + ;; NOTE `evilem-default-keybinds' unsets all other keys on the prefix (in + ;; motion state) + (evilem-default-keybindings prefix) + (evilem-define (kbd (concat prefix " n")) #'evil-ex-search-next) + (evilem-define (kbd (concat prefix " N")) #'evil-ex-search-previous) + (evilem-define (kbd (concat prefix " s")) #'evil-snipe-repeat + :pre-hook (save-excursion (call-interactively #'evil-snipe-s)) + :bind ((evil-snipe-scope 'buffer) + (evil-snipe-enable-highlight) + (evil-snipe-enable-incremental-highlight))) + (evilem-define (kbd (concat prefix " S")) #'evil-snipe-repeat-reverse + :pre-hook (save-excursion (call-interactively #'evil-snipe-s)) + :bind ((evil-snipe-scope 'buffer) + (evil-snipe-enable-highlight) + (evil-snipe-enable-incremental-highlight))))) + + +;; +;; Keybinding fixes +;; + +;; This section is dedicated to "fixing" certain keys so that they behave +;; properly, more like vim, or how I like it. + +(map! (:map input-decode-map + [S-iso-lefttab] [backtab] + (:unless window-system "TAB" [tab])) ; Fix TAB in terminal + + ;; I want C-a and C-e to be a little smarter. C-a will jump to + ;; indentation. Pressing it again will send you to the true bol. Same goes + ;; for C-e, except it will ignore comments and trailing whitespace before + ;; jumping to eol. + :i "C-a" #'doom/backward-to-bol-or-indent + :i "C-e" #'doom/forward-to-last-non-comment-or-eol + :i "C-u" #'doom/backward-kill-to-bol-and-indent + + ;; Emacsien motions for insert mode + :i "C-b" #'backward-word + :i "C-f" #'forward-word + + ;; Highjacks space/backspace to: + ;; a) balance spaces inside brackets/parentheses ( | ) -> (|) + ;; b) delete space-indented blocks intelligently + ;; c) do none of this when inside a string + ;; :i "SPC" #'doom/inflate-space-maybe + ;; :i [remap delete-backward-char] #'doom/deflate-space-maybe + ;; :i [remap newline] #'doom/newline-and-indent + + (:map org-mode-map + :i [remap doom/inflate-space-maybe] #'org-self-insert-command + "C-c C-x C-i" #'org-clock-in + "C-c C-x <C-i>" #'org-clock-in) + + (:map org-agenda-mode-map + "C-c C-x C-i" #'org-agenda-clock-in + "C-c C-x <C-i>" #'org-agenda-clock-in) + + ;; Restore common editing keys (and ESC) in minibuffer + (:map (minibuffer-local-map + minibuffer-local-ns-map + minibuffer-local-completion-map + minibuffer-local-must-match-map + minibuffer-local-isearch-map + evil-ex-completion-map + evil-ex-search-keymap + read-expression-map) + ;; [escape] #'abort-recursive-edit + "C-r" #'evil-paste-from-register + "C-a" #'move-beginning-of-line + "C-w" #'doom/minibuffer-kill-word + "C-u" #'doom/minibuffer-kill-line + "C-b" #'backward-word + "C-f" #'forward-word + "M-z" #'doom/minibuffer-undo) + + (:map messages-buffer-mode-map + "M-;" #'eval-expression + "A-;" #'eval-expression) + + (:map tabulated-list-mode-map + [remap evil-record-macro] #'doom/popup-close-maybe) + + (:after view + (:map view-mode-map "<escape>" #'View-quit-all))) + +(defun +sexp-transpose () + (interactive) + (case evil-this-operator + ('evil-shift-right (paxedit-transpose-forward)) + ('evil-shift-left (paxedit-transpose-backward)))) + +;; (defun nmap (&rest keys-and-ops) +;; (->> +;; (seq-partition keys-and-ops 2) +;; (seq-map +;; (lambda (k-op) +;; (let* ((k (car k-op)) +;; (op (cadr k-op)) +;; (prefix (substring k 0 1)) +;; (prefix-sym (lookup-key evil-normal-state-map prefix)) +;; (keyseq (substring k 1))) +;; (list keyseq prefix-sym op)))) +;; (seq-group-by #'car) +;; (seq-map +;; (lambda (k-ops) +;; (let* ((keyseq (car k-ops)) +;; (ops (cdr k-ops)) +;; (existing-binding (lookup-key evil-operator-state-map keyseq)) +;; (handler (ฮป! () +;; (if-let +;; ((oplist +;; (seq-find (lambda (op) +;; (equal (nth 1 op) +;; evil-this-operator)) +;; ops))) +;; (message "calling oplist") +;; (->> oplist (nth 2) funcall) +;; (when existing-binding +;; (funcall existing-binding)))))) +;; (if existing-binding +;; (progn +;; (define-key evil-operator-state-map +;; (vector 'remap existing-binding) +;; handler) +;; (define-key evil-motion-state-map +;; (vector 'remap existing-binding) +;; handler)) +;; (define-key evil-operator-state-map keyseq handler))))))) + +;; (nmap +;; ">e" #'paxedit-transpose-forward +;; "<e" #'paxedit-transpose-backward) + +(require 'paxedit) +(require 'general) +(general-evil-setup t) + +(nmap + ">" (general-key-dispatch 'evil-shift-right + "e" 'paxedit-transpose-forward + ")" 'sp-forward-slurp-sexp + "(" 'sp-backward-barf-sexp + "I" 'grfn/insert-at-sexp-end + ;; "a" 'grfn/insert-at-form-end + )) + +(nmap + "<" (general-key-dispatch 'evil-shift-left + "e" 'paxedit-transpose-backward + ")" 'sp-forward-barf-sexp + "(" 'sp-backward-slurp-sexp + "I" 'grfn/insert-at-sexp-start + ;; "a" 'grfn/insert-at-form-start + )) + + +(defmacro saving-excursion (&rest body) + `(ฮป! () (save-excursion ,@body))) + +(nmap "c" (general-key-dispatch 'evil-change + "r c" (saving-excursion (string-inflection-lower-camelcase)) + "r C" (saving-excursion (string-inflection-camelcase)) + "r m" (saving-excursion (string-inflection-camelcase)) + "r s" (saving-excursion (string-inflection-underscore)) + "r u" (saving-excursion (string-inflection-upcase)) + "r -" (saving-excursion (string-inflection-kebab-case)) + "r k" (saving-excursion (string-inflection-kebab-case)) + ;; "r ." (saving-excursion (string-inflection-dot-case)) + ;; "r ." (saving-excursion (string-inflection-space-case)) + ;; "r ." (saving-excursion (string-inflection-title-case)) + )) + + +(predd-defmulti eval-sexp (lambda (form) major-mode)) + +(predd-defmethod eval-sexp 'clojure-mode (form) + (cider-interactive-eval form)) + +(predd-defmethod eval-sexp 'emacs-lisp-mode (form) + (pp-eval-expression form)) + +(predd-defmulti eval-sexp-region (lambda (_beg _end) major-mode)) + +(predd-defmethod eval-sexp-region 'clojure-mode (beg end) + (cider-interactive-eval nil nil (list beg end))) + +(predd-defmethod eval-sexp-region 'emacs-lisp-mode (beg end) + (pp-eval-expression (read (buffer-substring beg end)))) + +(predd-defmulti eval-sexp-region-context (lambda (_beg _end _context) major-mode)) + +(predd-defmethod eval-sexp-region-context 'clojure-mode (beg end context) + (cider--eval-in-context (buffer-substring beg end))) + +(defun pp-eval-context-region (beg end context) + (interactive "r\nxContext: ") + (let* ((inner-expr (read (buffer-substring beg end))) + (full-expr (list 'let* context inner-expr))) + (pp-eval-expression full-expr))) + +(predd-defmethod eval-sexp-region-context 'emacs-lisp-mode (beg end context) + (pp-eval-context-region beg end context)) + +(predd-defmulti preceding-sexp (lambda () major-mode)) + +(predd-defmethod preceding-sexp 'clojure-mode () + (cider-last-sexp)) + +(predd-defmethod preceding-sexp 'emacs-lisp-mode () + (elisp--preceding-sexp)) + +(defun eval-sexp-at-point () + (interactive) + (let ((bounds (bounds-of-thing-at-point 'sexp))) + (eval-sexp-region (car bounds) + (cdr bounds)))) + +(defun eval-last-sexp (_) + (interactive) + (eval-sexp (preceding-sexp))) + +;;; + +(defun cider-insert-current-sexp-in-repl (&optional arg) + "Insert the expression at point in the REPL buffer. +If invoked with a prefix ARG eval the expression after inserting it" + (interactive "P") + (cider-insert-in-repl (cider-sexp-at-point) arg)) + +(evil-define-operator fireplace-send (beg end) + (cider-insert-current-sexp-in-repl nil nil (list beg end))) + +(defun +clojure-pprint-expr (form) + (format "(with-out-str (clojure.pprint/pprint %s))" + form)) + +(defun cider-eval-read-and-print-handler (&optional buffer) + "Make a handler for evaluating and reading then printing result in BUFFER." + (nrepl-make-response-handler + (or buffer (current-buffer)) + (lambda (buffer value) + (let ((value* (read value))) + (with-current-buffer buffer + (insert + (if (derived-mode-p 'cider-clojure-interaction-mode) + (format "\n%s\n" value*) + value*))))) + (lambda (_buffer out) (cider-emit-interactive-eval-output out)) + (lambda (_buffer err) (cider-emit-interactive-eval-err-output err)) + '())) + +(defun cider-eval-and-replace (beg end) + "Evaluate the expression in region and replace it with its result" + (interactive "r") + (let ((form (buffer-substring beg end))) + (cider-nrepl-sync-request:eval form) + (kill-region beg end) + (cider-interactive-eval + (+clojure-pprint-expr form) + (cider-eval-read-and-print-handler)))) + +(defun cider-eval-current-sexp-and-replace () + "Evaluate the expression at point and replace it with its result" + (interactive) + (apply #'cider-eval-and-replace (cider-sexp-at-point 'bounds))) + +;;; + +(evil-define-operator fireplace-eval (beg end) + (eval-sexp-region beg end)) + +(evil-define-operator fireplace-replace (beg end) + (cider-eval-and-replace beg end)) + +(evil-define-operator fireplace-eval-context (beg end) + (eval-sexp-region-context beg end)) + +;;; fireplace-esque eval binding +(nmap :keymaps 'cider-mode-map + "c" (general-key-dispatch 'evil-change + "p" (general-key-dispatch 'fireplace-eval + "p" 'cider-eval-sexp-at-point + "c" 'cider-eval-last-sexp + "d" 'cider-eval-defun-at-point + "r" 'cider-test-run-test) + "q" (general-key-dispatch 'fireplace-send + "q" 'cider-insert-current-sexp-in-repl + "c" 'cider-insert-last-sexp-in-repl) + "x" (general-key-dispatch 'fireplace-eval-context + "x" 'cider-eval-sexp-at-point-in-context + "c" 'cider-eval-last-sexp-in-context) + "!" (general-key-dispatch 'fireplace-replace + "!" 'cider-eval-current-sexp-and-replace + "c" 'cider-eval-last-sexp-and-replace) + "y" 'cider-copy-last-result)) + +;;; + +(nmap :keymaps 'emacs-lisp-mode-map + "c" (general-key-dispatch 'evil-change + "p" (general-key-dispatch 'fireplace-eval + "p" 'eval-sexp-at-point + "c" 'eval-last-sexp + "d" 'eval-defun + "r" 'cider-test-run-test) + "x" (general-key-dispatch 'fireplace-eval-context + "x" 'cider-eval-sexp-at-point-in-context + "c" 'cider-eval-last-sexp-in-context) + "!" (general-key-dispatch 'fireplace-replace + "!" 'cider-eval-current-sexp-and-replace + "c" 'cider-eval-last-sexp-and-replace) + "y" 'cider-copy-last-result)) + +(nmap :keymaps 'sly-mode-map + "c" (general-key-dispatch 'evil-change + "p" (general-key-dispatch 'sly-eval + ;; "p" 'eval-sexp-at-point + "c" 'sly-eval-last-expression + "d" 'sly-eval-defun + ;; "r" 'cider-test-run-test + ) + ;; "x" (general-key-dispatch 'fireplace-eval-context + ;; "x" 'cider-eval-sexp-at-point-in-context + ;; "c" 'cider-eval-last-sexp-in-context + ;; ) + ;; "!" (general-key-dispatch 'fireplace-replace + ;; "!" 'cider-eval-current-sexp-and-replace + ;; "c" 'cider-eval-last-sexp-and-replace) + ;; "y" 'cider-copy-last-result + )) + + +;; >) ; slurp forward +;; <) ; barf forward +;; <( ; slurp backward +;; >( ; slurp backward + +;; (require 'doom-themes) +(defun grfn/haskell-test-file-p () + (string-match-p (rx (and "Spec.hs" eol)) + (buffer-file-name))) + +(require 'haskell) + +(defun grfn/intero-run-main () + (interactive) + (intero-repl-load) + (intero-with-repl-buffer nil + (comint-simple-send + (get-buffer-process (current-buffer)) + "main"))) + +(defun grfn/run-clj-or-cljs-test () + (interactive) + (message "Running tests...") + (cl-case (cider-repl-type-for-buffer) + ('cljs + (cider-interactive-eval + "(with-out-str (cljs.test/run-tests))" + (nrepl-make-response-handler + (current-buffer) + (lambda (_ value) + (with-output-to-temp-buffer "*cljs-test-results*" + (print + (->> value + (s-replace "\"" "") + (s-replace "\\n" "\n"))))) + nil nil nil))) + (('clj 'multi) + (funcall-interactively + #'cider-test-run-ns-tests + nil)))) + +(defun cider-copy-last-result () + (interactive) + (cider-interactive-eval + "*1" + (nrepl-make-response-handler + (current-buffer) + (lambda (_ value) + (kill-new value) + (message "Copied last result (%s) to clipboard" + (if (= (length value) 1) "1 char" + (format "%d chars" (length value))))) + nil nil nil))) + + +(defun grfn/insert-new-src-block () + (interactive) + (let* ((current-src-block (org-element-at-point)) + (src-block-head (save-excursion + (goto-char (org-element-property + :begin current-src-block)) + (let ((line (thing-at-point 'line t))) + (if (not (s-starts-with? "#+NAME:" (s-trim line))) + line + (forward-line) + (thing-at-point 'line t))))) + (point-to-insert + (if-let (results-loc (org-babel-where-is-src-block-result)) + (save-excursion + (goto-char results-loc) + (org-element-property + :end + (org-element-at-point))) + (org-element-property :end (org-element-at-point))))) + (goto-char point-to-insert) + (insert "\n") + (insert src-block-head) + (let ((contents (point-marker))) + (insert "\n#+END_SRC\n") + (goto-char contents)))) + +(defun grfn/+org-insert-item (orig direction) + (interactive) + (if (and (org-in-src-block-p) + (equal direction 'below)) + (grfn/insert-new-src-block) + (funcall orig direction))) + +(advice-add #'+org--insert-item :around #'grfn/+org-insert-item) +;; (advice-add #'+org/insert-item-below :around +;; (lambda (orig) (grfn/+org-insert-item orig 'below))) + +(defun set-pdb-trace () + (interactive) + (end-of-line) + (insert (format "\n%simport pdb;pdb.set_trace()" + (make-string (python-indent-calculate-indentation) + ?\s))) + (evil-indent (line-beginning-position) + (line-end-position))) + +(map! + + (:map magit-mode-map + :n "#" 'forge-dispatch) + + (:map haskell-mode-map + :n "K" 'lsp-info-under-point + :n "g d" 'lsp-ui-peek-find-definitions + :n "g r" 'lsp-ui-peek-find-references + :n "g \\" '+haskell/repl + ;; :n "K" 'intero-info + ;; :n "g d" 'intero-goto-definition + ;; :n "g SPC" 'intero-repl-load + ;; :n "g \\" 'intero-repl + ;; :n "g y" 'intero-type-at + ;; :n "g RET" 'grfn/run-sputnik-test-for-file + + (:localleader + :desc "Apply action" :n "e" 'intero-repl-eval-region + :desc "Rename symbol" :n "r" 'intero-apply-suggestions)) + + (:map python-mode-map + :n "K" #'anaconda-mode-show-doc + :n "g SPC" #'+eval/buffer + :n "g RET" #'python-pytest-file + :n "g \\" #'+python/open-ipython-repl + [remap evil-commentary-yank] #'set-pdb-trace) + + (:after agda2-mode + (:map agda2-mode-map + :n "g SPC" 'agda2-load + :n "g d" 'agda2-goto-definition-keyboard + :n "] g" 'agda2-next-goal + :n "[ g" 'agda2-previous-goal + + (:localleader + :desc "Give" :n "SPC" 'agda2-give + :desc "Case Split" :n "c" 'agda2-make-case + :desc "Make Helper" :n "h" 'agda2-helper-function-type + :desc "Refine" :n "r" 'agda2-refine + :desc "Auto" :n "a" 'agda2-auto-maybe-all + :desc "Goal type and context" :n "t" 'agda2-goal-and-context + :desc "Goal type and context and inferred" :n ";" 'agda2-goal-and-context-and-inferred))) + + (:after clojure-mode + (:map clojure-mode-map + :n "] f" 'forward-sexp + :n "[ f" 'backward-sexp)) + + (:after cider-mode + (:map cider-mode-map + :n "g SPC" 'cider-eval-buffer + :n "g \\" 'cider-switch-to-repl-buffer + :n "K" 'cider-doc + :n "g K" 'cider-grimoire + :n "g d" 'cider-find-dwim + :n "C-w ]" 'cider-find-dwim-other-window + ;; :n "g RET" 'cider-test-run-ns-tests + :n "g RET" 'grfn/run-clj-or-cljs-test + + "C-c C-r r" 'cljr-add-require-to-ns + "C-c C-r i" 'cljr-add-import-to-ns + + (:localleader + ;; :desc "Inspect last result" :n "i" 'cider-inspect-last-result + ;; :desc "Search for documentation" :n "h s" 'cider-apropos-doc + :desc "Add require to ns" :n "n r" 'cljr-add-require-to-ns + :desc "Add import to ns" :n "n i" 'cljr-add-import-to-ns)) + (:map cider-repl-mode-map + :n "g \\" 'cider-switch-to-last-clojure-buffer)) + + (:after w3m + (:map w3m-mode-map + "/" #'evil-search-forward + "?" #'evil-search-backward + "r" #'w3m-reload-this-page)) + + (:after slack + (:map slack-message-buffer-mode-map + :i "<up>" #'slack-message-edit)) + + (:after org + :n "C-c C-x C-o" #'org-clock-out + (:map org-mode-map + [remap counsel-imenu] #'counsel-org-goto + "M-k" #'org-move-subtree-up + "M-j" #'org-move-subtree-down + (:localleader + :n "g" #'counsel-org-goto)) + + (:map org-capture-mode-map + :n "g RET" #'org-capture-finalize + :n "g \\" #'org-captue-refile)) + + (:map lsp-mode-map + :n "K" #'lsp-describe-thing-at-point + :n "g r" #'lsp-rename + (:localleader + :n "a" #'lsp-execute-code-action)) + + (:map prolog-mode-map + :n "g SPC" #'prolog-compile-buffer + :n "g \\" #'run-prolog)) diff --git a/users/grfn/emacs.d/+commands.el b/users/grfn/emacs.d/+commands.el new file mode 100644 index 000000000000..518f185cb9c2 --- /dev/null +++ b/users/grfn/emacs.d/+commands.el @@ -0,0 +1,149 @@ +;; -*- lexical-binding: t; -*- + +(defalias 'ex! 'evil-ex-define-cmd) + +(defun delete-file-and-buffer () + "Kill the current buffer and deletes the file it is visiting." + (interactive) + (let ((filename (buffer-file-name))) + (when filename + (if (vc-backend filename) + (vc-delete-file filename) + (progn + (delete-file filename) + (message "Deleted file %s" filename) + (kill-buffer)))))) + +;;; Commands defined elsewhere +;;(ex! "al[ign]" #'+evil:align) +;;(ex! "g[lobal]" #'+evil:global) + +;;; Custom commands +;; Editing +(ex! "@" #'+evil:macro-on-all-lines) ; TODO Test me +(ex! "al[ign]" #'+evil:align) +(ex! "enhtml" #'+web:encode-html-entities) +(ex! "dehtml" #'+web:decode-html-entities) +(ex! "mc" #'+evil:mc) +(ex! "iedit" #'evil-multiedit-ex-match) +(ex! "na[rrow]" #'+evil:narrow-buffer) +(ex! "retab" #'+evil:retab) + +(ex! "glog" #'magit-log-buffer-file) + +;; External resources +;; TODO (ex! "db" #'doom:db) +;; TODO (ex! "dbu[se]" #'doom:db-select) +;; TODO (ex! "go[ogle]" #'doom:google-search) +(ex! "lo[okup]" #'+jump:online) +(ex! "dash" #'+lookup:dash) +(ex! "dd" #'+lookup:devdocs) +(ex! "http" #'httpd-start) ; start http server +(ex! "repl" #'+eval:repl) ; invoke or send to repl +;; TODO (ex! "rx" 'doom:regex) ; open re-builder +(ex! "sh[ell]" #'+eshell:run) +(ex! "t[mux]" #'+tmux:run) ; send to tmux +(ex! "tcd" #'+tmux:cd-here) ; cd to default-directory in tmux +(ex! "x" #'doom/open-project-scratch-buffer) + +;; GIT +(ex! "gist" #'+gist:send) ; send current buffer/region to gist +(ex! "gistl" #'+gist:list) ; list gists by user +(ex! "gbrowse" #'+vcs/git-browse) ; show file in github/gitlab +(ex! "gissues" #'+vcs/git-browse-issues) ; show github issues +(ex! "git" #'magit-status) ; open magit status window +(ex! "gstage" #'magit-stage) +(ex! "gunstage" #'magit-unstage) +(ex! "gblame" #'magit-blame) +(ex! "grevert" #'git-gutter:revert-hunk) + +;; Dealing with buffers +(ex! "clean[up]" #'doom/cleanup-buffers) +(ex! "k[ill]" #'doom/kill-this-buffer) +(ex! "k[ill]all" #'+hlissner:kill-all-buffers) +(ex! "k[ill]m" #'+hlissner:kill-matching-buffers) +(ex! "k[ill]o" #'doom/kill-other-buffers) +(ex! "l[ast]" #'doom/popup-restore) +(ex! "m[sg]" #'view-echo-area-messages) +(ex! "pop[up]" #'doom/popup-this-buffer) + +;; Project navigation +(ex! "a" #'projectile-toggle-between-implementation-and-test) +(ex! "as" #'projectile-find-implementation-or-test-other-window) +(ex! "av" #'projectile-find-implementation-or-test-other-window) +(ex! "cd" #'+hlissner:cd) +(cond ((featurep! :completion ivy) + (ex! "ag" #'+ivy:ag) + (ex! "agc[wd]" #'+ivy:ag-cwd) + (ex! "rg" #'+ivy:rg) + (ex! "rgc[wd]" #'+ivy:rg-cwd) + (ex! "sw[iper]" #'+ivy:swiper) + (ex! "todo" #'+ivy:todo)) + ((featurep! :completion helm) + (ex! "ag" #'+helm:ag) + (ex! "agc[wd]" #'+helm:ag-cwd) + (ex! "rg" #'+helm:rg) + (ex! "rgc[wd]" #'+helm:rg-cwd) + (ex! "sw[oop]" #'+helm:swoop) + (ex! "todo" #'+helm:todo))) + +;; Project tools +(ex! "build" #'+eval/build) +(ex! "debug" #'+debug/run) +(ex! "er[rors]" #'flycheck-list-errors) + +;; File operations +(ex! "cp" #'+evil:copy-this-file) +(ex! "mv" #'+evil:move-this-file) +(ex! "rm" #'+evil:delete-this-file) + +;; Sessions/tabs +(ex! "sclear" #'+workspace/kill-session) +(ex! "sl[oad]" #'+workspace:load-session) +(ex! "ss[ave]" #'+workspace:save-session) +(ex! "tabcl[ose]" #'+workspace:delete) +(ex! "tabclear" #'doom/kill-all-buffers) +(ex! "tabl[ast]" #'+workspace/switch-to-last) +(ex! "tabload" #'+workspace:load) +(ex! "tabn[ew]" #'+workspace:new) +(ex! "tabn[ext]" #'+workspace:switch-next) +(ex! "tabp[rev]" #'+workspace:switch-previous) +(ex! "tabr[ename]" #'+workspace:rename) +(ex! "tabs" #'+workspace/display) +(ex! "tabsave" #'+workspace:save) + +(ex! "scr[atch]" #'cider-scratch) + +;; Org-mode +(ex! "cap" #'+org-capture/dwim) + +(evil-define-command evil-alembic-revision (args) + (interactive "<a>") + (apply + #'generate-alembic-migration + (read-string "Message: ") + (s-split "\\s+" (or args "")))) +(ex! "arev[ision]" #'evil-alembic-revision) + +(evil-define-command evil-alembic-upgrade (&optional revision) + (interactive "<a>") + (alembic-upgrade (or revision "head"))) + +(ex! "aup[grade]" #'evil-alembic-upgrade) + +(evil-define-command evil-alembic-downgrade (&optional revision) + (interactive "<a>") + (alembic-downgrade revision)) + +(ex! "adown[grade]" #'evil-alembic-downgrade) + +(evil-define-command evil-alembic (args) + (interactive "<a>") + (run-alembic args)) + +(ex! "alemb[ic]" #'evil-alembic) + +;; Elixir +(add-hook! elixir-mode + (ex! "AV" #'alchemist-project-toggle-file-and-tests-other-window) + (ex! "A" #'alchemist-project-toggle-file-and-tests)) diff --git a/users/grfn/emacs.d/+private.el.gpg b/users/grfn/emacs.d/+private.el.gpg new file mode 100644 index 000000000000..6273c67d6ecd --- /dev/null +++ b/users/grfn/emacs.d/+private.el.gpg Binary files differdiff --git a/users/grfn/emacs.d/.gitignore b/users/grfn/emacs.d/.gitignore new file mode 100644 index 000000000000..1fd0e3988771 --- /dev/null +++ b/users/grfn/emacs.d/.gitignore @@ -0,0 +1,2 @@ +.authinfo.gpg ++private.el diff --git a/users/grfn/emacs.d/autoload/evil.el b/users/grfn/emacs.d/autoload/evil.el new file mode 100644 index 000000000000..319c93c05e47 --- /dev/null +++ b/users/grfn/emacs.d/autoload/evil.el @@ -0,0 +1,37 @@ +;;; /autoload/evil.el -*- lexical-binding: t; -*- +;;;###if (featurep! :feature evil) + +;;;###autoload (autoload '+hlissner:multi-next-line "/autoload/evil" nil t) +(evil-define-motion +hlissner:multi-next-line (count) + "Move down 6 lines." + :type line + (let ((line-move-visual (or visual-line-mode (derived-mode-p 'text-mode)))) + (evil-line-move (* 6 (or count 1))))) + +;;;###autoload (autoload '+hlissner:multi-previous-line "/autoload/evil" nil t) +(evil-define-motion +hlissner:multi-previous-line (count) + "Move up 6 lines." + :type line + (let ((line-move-visual (or visual-line-mode (derived-mode-p 'text-mode)))) + (evil-line-move (- (* 6 (or count 1)))))) + +;;;###autoload (autoload '+hlissner:cd "/autoload/evil" nil t) +(evil-define-command +hlissner:cd () + "Change `default-directory' with `cd'." + (interactive "<f>") + (cd input)) + +;;;###autoload (autoload '+hlissner:kill-all-buffers "/autoload/evil" nil t) +(evil-define-command +hlissner:kill-all-buffers (&optional bang) + "Kill all buffers. If BANG, kill current session too." + (interactive "<!>") + (if bang + (+workspace/kill-session) + (doom/kill-all-buffers))) + +;;;###autoload (autoload '+hlissner:kill-matching-buffers "/autoload/evil" nil t) +(evil-define-command +hlissner:kill-matching-buffers (&optional bang pattern) + "Kill all buffers matching PATTERN regexp. If BANG, only match project +buffers." + (interactive "<a>") + (doom/kill-matching-buffers pattern bang)) diff --git a/users/grfn/emacs.d/autoload/hlissner.el b/users/grfn/emacs.d/autoload/hlissner.el new file mode 100644 index 000000000000..87b2236d12c7 --- /dev/null +++ b/users/grfn/emacs.d/autoload/hlissner.el @@ -0,0 +1,53 @@ +;;; autoload/hlissner.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +hlissner/install-snippets () + "Install my snippets from https://github.com/hlissner/emacs-snippets into +private/hlissner/snippets." + (interactive) + (doom-fetch :github "hlissner/emacs-snippets" + (expand-file-name "snippets" (doom-module-path :private 'hlissner)))) + +;;;###autoload +(defun +hlissner/yank-buffer-filename () + "Copy the current buffer's path to the kill ring." + (interactive) + (if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory)))) + (message (kill-new (abbreviate-file-name filename))) + (error "Couldn't find filename in current buffer"))) + +(defmacro +hlissner-def-finder! (name dir) + "Define a pair of find-file and browse functions." + `(progn + (defun ,(intern (format "+hlissner/find-in-%s" name)) () + (interactive) + (let ((default-directory ,dir) + projectile-project-name + projectile-require-project-root + projectile-cached-buffer-file-name + projectile-cached-project-root) + (call-interactively (command-remapping #'projectile-find-file)))) + (defun ,(intern (format "+hlissner/browse-%s" name)) () + (interactive) + (let ((default-directory ,dir)) + (call-interactively (command-remapping #'find-file)))))) + +;;;###autoload (autoload '+hlissner/find-in-templates "autoload/hlissner" nil t) +;;;###autoload (autoload '+hlissner/browse-templates "autoload/hlissner" nil t) +(+hlissner-def-finder! templates +file-templates-dir) + +;;;###autoload (autoload '+hlissner/find-in-snippets "autoload/hlissner" nil t) +;;;###autoload (autoload '+hlissner/browse-snippets "autoload/hlissner" nil t) +(+hlissner-def-finder! snippets +hlissner-snippets-dir) + +;;;###autoload (autoload '+hlissner/find-in-dotfiles "autoload/hlissner" nil t) +;;;###autoload (autoload '+hlissner/browse-dotfiles "autoload/hlissner" nil t) +(+hlissner-def-finder! dotfiles (expand-file-name ".dotfiles" "~")) + +;;;###autoload (autoload '+hlissner/find-in-emacsd "autoload/hlissner" nil t) +;;;###autoload (autoload '+hlissner/browse-emacsd "autoload/hlissner" nil t) +(+hlissner-def-finder! emacsd doom-emacs-dir) + +;;;###autoload (autoload '+hlissner/find-in-notes "autoload/hlissner" nil t) +;;;###autoload (autoload '+hlissner/browse-notes "autoload/hlissner" nil t) +(+hlissner-def-finder! notes +org-dir) diff --git a/users/grfn/emacs.d/clocked-in-elt.el b/users/grfn/emacs.d/clocked-in-elt.el new file mode 100644 index 000000000000..be4161441dce --- /dev/null +++ b/users/grfn/emacs.d/clocked-in-elt.el @@ -0,0 +1,17 @@ +;;; -*- lexical-binding: t; -*- +(load (expand-file-name "init" (or (getenv "EMACSDIR") + (expand-file-name + "../.emacs.d" + (file-name-directory (file-truename load-file-name)))))) + +(require 'org-clock) +(require 'org-element) + +(let ((item (or org-clock-marker + (car org-clock-history)))) + (when item + (with-current-buffer (marker-buffer item) + (goto-char (marker-position item)) + (let ((element (org-element-at-point))) + (when (eq 'headline (car element)) + (message "%s" (plist-get (cadr element) :raw-value))))))) diff --git a/users/grfn/emacs.d/clojure.el b/users/grfn/emacs.d/clojure.el new file mode 100644 index 000000000000..f001a3e12b68 --- /dev/null +++ b/users/grfn/emacs.d/clojure.el @@ -0,0 +1,53 @@ +;;; -*- lexical-binding: t; -*- + +(defun clojure-thing-at-point-setup () + (interactive) + ;; Used by cider-find-dwim to parse the symbol at point + (setq-local + thing-at-point-file-name-chars + (concat thing-at-point-file-name-chars + "><!?"))) + +(defun +grfn/clojure-setup () + ;; (flycheck-select-checker 'clj-kondo) + (require 'flycheck) + (push 'clojure-cider-kibit flycheck-disabled-checkers) + (push 'clojure-cider-eastwood flycheck-disabled-checkers) + (push 'clojure-cider-typed flycheck-disabled-checkers) + ) + +(after! clojure-mode + (define-clojure-indent + (PUT 2) + (POST 2) + (GET 2) + (PATCH 2) + (DELETE 2) + (context 2) + (checking 3) + (match 1) + (domonad 0) + (describe 1) + (before 1) + (it 2)) + + (add-hook 'clojure-mode-hook #'clojure-thing-at-point-setup) + (add-hook 'clojure-mode-hook #'+grfn/clojure-setup)) + +(use-package! flycheck-clojure + ;; :disabled t + :after (flycheck cider) + :config + (flycheck-clojure-setup)) + +(after! clj-refactor + (setq cljr-magic-requires :prompt + cljr-clojure-test-declaration "[clojure.test :refer :all]" + cljr-cljc-clojure-test-declaration"#?(:clj [clojure.test :refer :all] +:cljs [cljs.test :refer-macros [deftest is testing]])" + ) + (add-to-list + 'cljr-magic-require-namespaces + '("s" . "clojure.spec.alpha"))) + +(set-popup-rule! "^\\*cider-test-report" :size 0.4) diff --git a/users/grfn/emacs.d/company-sql.el b/users/grfn/emacs.d/company-sql.el new file mode 100644 index 000000000000..e623aa2de131 --- /dev/null +++ b/users/grfn/emacs.d/company-sql.el @@ -0,0 +1,299 @@ +;;; Commentary: +;;; TODO + +;;; Code: + +(require 'emacsql) +(require 'emacsql-psql) +(require 'dash) +(require 's) +(require 'cl-lib) + +;;; Config + +(defvar-local company-sql-db-host "localhost" + "Host of the postgresql database to query for autocomplete information") + +(defvar-local company-sql-db-port 5432 + "Port of the postgresql database to query for autocomplete information") + +(defvar-local company-sql-db-user "postgres" + "Username of the postgresql database to query for autocomplete information") + +(defvar-local company-sql-db-name nil + "PostgreSQL database name to query for autocomplete information") + +;;; DB Connection + +(defvar-local company-sql/connection nil) + +(defun company-sql/connect () + (unless company-sql/connection + (setq-local company-sql/connection + (emacsql-psql company-sql-db-name + :hostname company-sql-db-host + :username company-sql-db-user + :port (number-to-string company-sql-db-port)))) + company-sql/connection) + +;;; Utils + +(defmacro comment (&rest _)) + +(defun ->string (x) + (cond + ((stringp x) x) + ((symbolp x) (symbol-name x)))) + +(defun alist-get-equal (key alist) + "Like `alist-get', but uses `equal' instead of `eq' for comparing keys" + (->> alist + (-find (lambda (pair) (equal key (car pair)))) + (cdr))) + +;;; Listing relations + +(cl-defun company-sql/list-tables (conn) + (with-timeout (3) + (-map (-compose 'symbol-name 'car) + (emacsql conn + [:select [tablename] + :from pg_catalog:pg_tables + :where (and (!= schemaname '"information_schema") + (!= schemaname '"pg_catalog"))])))) + +(cl-defun company-sql/list-columns (conn) + (with-timeout (3) + (-map + (lambda (row) + (propertize (symbol-name (nth 0 row)) + 'table-name (nth 1 row) + 'data-type (nth 2 row))) + (emacsql conn + [:select [column_name + table_name + data_type] + :from information_schema:columns])))) + +;;; Keywords + +(defvar company-postgresql/keywords + (list +"a" "abort" "abs" "absent" "absolute" "access" "according" "action" "ada" "add" +"admin" "after" "aggregate" "all" "allocate" "also" "alter" "always" "analyse" +"analyze" "and" "any" "are" "array" "array_agg" "array_max_cardinality" "as" +"asc" "asensitive" "assertion" "assignment" "asymmetric" "at" "atomic" "attach" +"attribute" "attributes" "authorization" "avg" "backward" "base64" "before" +"begin" "begin_frame" "begin_partition" "bernoulli" "between" "bigint" "binary" +"bit" "bit_length" "blob" "blocked" "bom" "boolean" "both" "breadth" "by" "c" +"cache" "call" "called" "cardinality" "cascade" "cascaded" "case" "cast" +"catalog" "catalog_name" "ceil" "ceiling" "chain" "char" "character" +"characteristics" "characters" "character_length" "character_set_catalog" +"character_set_name" "character_set_schema" "char_length" "check" "checkpoint" +"class" "class_origin" "clob" "close" "cluster" "coalesce" "cobol" "collate" +"collation" "collation_catalog" "collation_name" "collation_schema" "collect" +"column" "columns" "column_name" "command_function" "command_function_code" +"comment" "comments" "commit" "committed" "concurrently" "condition" +"condition_number" "configuration" "conflict" "connect" "connection" +"connection_name" "constraint" "constraints" "constraint_catalog" +"constraint_name" "constraint_schema" "constructor" "contains" "content" +"continue" "control" "conversion" "convert" "copy" "corr" "corresponding" "cost" +"count" "covar_pop" "covar_samp" "create" "cross" "csv" "cube" "cume_dist" +"current" "current_catalog" "current_date" "current_default_transform_group" +"current_path" "current_role" "current_row" "current_schema" "current_time" +"current_timestamp" "current_transform_group_for_type" "current_user" "cursor" +"cursor_name" "cycle" "data" "database" "datalink" "date" +"datetime_interval_code" "datetime_interval_precision" "day" "db" "deallocate" +"dec" "decimal" "declare" "default" "defaults" "deferrable" "deferred" "defined" +"definer" "degree" "delete" "delimiter" "delimiters" "dense_rank" "depends" +"depth" "deref" "derived" "desc" "describe" "descriptor" "detach" +"deterministic" "diagnostics" "dictionary" "disable" "discard" "disconnect" +"dispatch" "distinct" "dlnewcopy" "dlpreviouscopy" "dlurlcomplete" +"dlurlcompleteonly" "dlurlcompletewrite" "dlurlpath" "dlurlpathonly" +"dlurlpathwrite" "dlurlscheme" "dlurlserver" "dlvalue" "do" "document" "domain" +"double" "drop" "dynamic" "dynamic_function" "dynamic_function_code" "each" +"element" "else" "empty" "enable" "encoding" "encrypted" "end" "end-exec" +"end_frame" "end_partition" "enforced" "enum" "equals" "escape" "event" "every" +"except" "exception" "exclude" "excluding" "exclusive" "exec" "execute" "exists" +"exp" "explain" "expression" "extension" "external" "extract" "false" "family" +"fetch" "file" "filter" "final" "first" "first_value" "flag" "float" "floor" +"following" "for" "force" "foreign" "fortran" "forward" "found" "frame_row" +"free" "freeze" "from" "fs" "full" "function" "functions" "fusion" "g" "general" +"generated" "get" "global" "go" "goto" "grant" "granted" "greatest" "group" +"grouping" "groups" "handler" "having" "header" "hex" "hierarchy" "hold" "hour" +"id" "identity" "if" "ignore" "ilike" "immediate" "immediately" "immutable" +"implementation" "implicit" "import" "in" "include" "including" "increment" +"indent" "index" "indexes" "indicator" "inherit" "inherits" "initially" "inline" +"inner" "inout" "input" "insensitive" "insert" "instance" "instantiable" +"instead" "int" "integer" "integrity" "intersect" "intersection" "interval" +"into" "invoker" "is" "isnull" "isolation" "join" "k" "key" "key_member" +"key_type" "label" "lag" "language" "large" "last" "last_value" "lateral" "lead" +"leading" "leakproof" "least" "left" "length" "level" "library" "like" +"like_regex" "limit" "link" "listen" "ln" "load" "local" "localtime" +"localtimestamp" "location" "locator" "lock" "locked" "logged" "lower" "m" "map" +"mapping" "match" "matched" "materialized" "max" "maxvalue" "max_cardinality" +"member" "merge" "message_length" "message_octet_length" "message_text" "method" +"min" "minute" "minvalue" "mod" "mode" "modifies" "module" "month" "more" "move" +"multiset" "mumps" "name" "names" "namespace" "national" "natural" "nchar" +"nclob" "nesting" "new" "next" "nfc" "nfd" "nfkc" "nfkd" "nil" "no" "none" +"normalize" "normalized" "not" "nothing" "notify" "notnull" "nowait" "nth_value" +"ntile" "null" "nullable" "nullif" "nulls" "number" "numeric" "object" +"occurrences_regex" "octets" "octet_length" "of" "off" "offset" "oids" "old" +"on" "only" "open" "operator" "option" "options" "or" "order" "ordering" +"ordinality" "others" "out" "outer" "output" "over" "overlaps" "overlay" +"overriding" "owned" "owner" "p" "pad" "parallel" "parameter" "parameter_mode" +"parameter_name" "parameter_ordinal_position" "parameter_specific_catalog" +"parameter_specific_name" "parameter_specific_schema" "parser" "partial" +"partition" "pascal" "passing" "passthrough" "password" "path" "percent" +"percentile_cont" "percentile_disc" "percent_rank" "period" "permission" +"placing" "plans" "pli" "policy" "portion" "position" "position_regex" "power" +"precedes" "preceding" "precision" "prepare" "prepared" "preserve" "primary" +"prior" "privileges" "procedural" "procedure" "procedures" "program" "public" +"publication" "quote" "range" "rank" "read" "reads" "real" "reassign" "recheck" +"recovery" "recursive" "ref" "references" "referencing" "refresh" "regr_avgx" +"regr_avgy" "regr_count" "regr_intercept" "regr_r2" "regr_slope" "regr_sxx" +"regr_sxy" "regr_syy" "reindex" "relative" "release" "rename" "repeatable" +"replace" "replica" "requiring" "reset" "respect" "restart" "restore" "restrict" +"result" "return" "returned_cardinality" "returned_length" +"returned_octet_length" "returned_sqlstate" "returning" "returns" "revoke" +"right" "role" "rollback" "rollup" "routine" "routines" "routine_catalog" +"routine_name" "routine_schema" "row" "rows" "row_count" "row_number" "rule" +"savepoint" "scale" "schema" "schemas" "schema_name" "scope" "scope_catalog" +"scope_name" "scope_schema" "scroll" "search" "second" "section" "security" +"select" "selective" "self" "sensitive" "sequence" "sequences" "serializable" +"server" "server_name" "session" "session_user" "set" "setof" "sets" "share" +"show" "similar" "simple" "size" "skip" "smallint" "snapshot" "some" "source" +"space" "specific" "specifictype" "specific_name" "sql" "sqlcode" "sqlerror" +"sqlexception" "sqlstate" "sqlwarning" "sqrt" "stable" "standalone" "start" +"state" "statement" "static" "statistics" "stddev_pop" "stddev_samp" "stdin" +"stdout" "storage" "strict" "strip" "structure" "style" "subclass_origin" +"submultiset" "subscription" "substring" "substring_regex" "succeeds" "sum" +"symmetric" "sysid" "system" "system_time" "system_user" "t" "table" "tables" +"tablesample" "tablespace" "table_name" "temp" "template" "temporary" "text" +"then" "ties" "time" "timestamp" "timezone_hour" "timezone_minute" "to" "token" +"top_level_count" "trailing" "transaction" "transactions_committed" +"transactions_rolled_back" "transaction_active" "transform" "transforms" +"translate" "translate_regex" "translation" "treat" "trigger" "trigger_catalog" +"trigger_name" "trigger_schema" "trim" "trim_array" "true" "truncate" "trusted" +"type" "types" "uescape" "unbounded" "uncommitted" "under" "unencrypted" "union" +"unique" "unknown" "unlink" "unlisten" "unlogged" "unnamed" "unnest" "until" +"untyped" "update" "upper" "uri" "usage" "user" "user_defined_type_catalog" +"user_defined_type_code" "user_defined_type_name" "user_defined_type_schema" +"using" "vacuum" "valid" "validate" "validator" "value" "values" "value_of" +"varbinary" "varchar" "variadic" "varying" "var_pop" "var_samp" "verbose" +"version" "versioning" "view" "views" "volatile" "when" "whenever" "where" +"whitespace" "width_bucket" "window" "with" "within" "without" "work" "wrapper" +"write" "xml" "xmlagg" "xmlattributes" "xmlbinary" "xmlcast" "xmlcomment" +"xmlconcat" "xmldeclaration" "xmldocument" "xmlelement" "xmlexists" "xmlforest" +"xmliterate" "xmlnamespaces" "xmlparse" "xmlpi" "xmlquery" "xmlroot" "xmlschema" +"xmlserialize" "xmltable" "xmltext" "xmlvalidate" "year" "yes" "zone")) + +;;; Company backend + +(cl-defun company-postgresql/candidates (prefix conn) + (-filter + (apply-partially #'s-starts-with? prefix) + (append (-map (lambda (s) + (propertize s 'company-postgresql-annotation "table")) + + (-map (lambda (s) + (propertize s 'company-postgresql-annotation + (format "%s.%s %s" + (get-text-property 0 'table-name s) + s + (-> + (get-text-property 0 'data-type s) + (->string) + (upcase))))) + (company-sql/list-columns conn)) + (-map (lambda (s) + (propertize s 'company-postgresql-annotation "keyword")) + company-postgresql/keywords))))) + +(defun company-postgresql (command &optional arg &rest _) + (interactive (list 'interactive)) + (cl-case command + (interactive (company-begin-backend 'company-postgresql)) + (init (company-sql/connect)) + (prefix (company-grab-symbol)) + (annotation + (get-text-property 0 'company-postgresql-annotation arg)) + (candidates (company-postgresql/candidates + arg + (company-sql/connect))) + (duplicates t) + (ignore-case t))) + +;;; org-babel company sql + +(defvar-local org-company-sql/connections + ()) + +(defun org-company-sql/connect (conn-params) + (or (alist-get-equal conn-params org-company-sql/connections) + (let ((conn (apply 'emacsql-psql conn-params))) + (add-to-list 'org-company-sql/connections (cons conn-params conn)) + conn))) + +(defun org-company-sql/in-sql-source-block-p () + (let ((org-elt (org-element-at-point))) + (and (eq 'src-block (car org-elt)) + (equal "sql" (plist-get (cadr org-elt) + :language))))) + +(defun org-company-sql/parse-cmdline (cmdline) + (let* ((lexed (s-split (rx (one-or-more blank)) cmdline)) + (go (lambda (state tokens) + (if (null tokens) () + (let ((token (car tokens)) + (tokens (cdr tokens))) + (if (null state) + (if (s-starts-with? "-" token) + (funcall go token tokens) + (cons token (funcall go state tokens))) + (cons (cons state token) ; ("-h" . "localhost") + (funcall go nil tokens))))))) + (opts (funcall go nil lexed))) + opts)) + +(defun org-company-sql/source-block-conn-params () + (let* ((block-info (org-babel-get-src-block-info)) + (params (caddr block-info)) + (cmdline (alist-get :cmdline params)) + (parsed (org-company-sql/parse-cmdline cmdline)) + (opts (-filter #'listp parsed)) + (positional (-filter #'stringp parsed)) + (host (alist-get-equal "-h" opts)) + (port (or (alist-get-equal "-p" opts) + "5432")) + (dbname (or (alist-get-equal "-d" opts) + (car positional))) + (username (or (alist-get-equal "-U" opts) + (cadr positional)))) + (list dbname + :hostname host + :username username + :port port))) + +(defun org-company-sql/connection-for-source-block () + (org-company-sql/connect + (org-company-sql/source-block-conn-params))) + + +(defun company-ob-postgresql (command &optional arg &rest _) + (interactive (list 'interactive)) + (cl-case command + (interactive (company-begin-backend 'company-ob-postgresql)) + (prefix (and (org-company-sql/in-sql-source-block-p) + (company-grab-symbol))) + (annotation (get-text-property 0 'company-postgresql-annotation arg)) + (candidates + (company-postgresql/candidates + arg + (org-company-sql/connection-for-source-block))) + (duplicates t) + (ignore-case t))) + +;;; + +(provide 'company-sql) diff --git a/users/grfn/emacs.d/config.el b/users/grfn/emacs.d/config.el new file mode 100644 index 000000000000..5312fd92b119 --- /dev/null +++ b/users/grfn/emacs.d/config.el @@ -0,0 +1,1083 @@ +;;; -*- lexical-binding: t; -*- + +;; I've swapped these keys on my keyboard +(setq x-super-keysym 'alt + x-alt-keysym 'meta) + +(setq user-mail-address "root@gws.fyi" + user-full-name "Griffin Smith") + +(let ((font-family (pcase system-type + ('darwin "MesloLGSDZ NF") + ('gnu/linux "Meslo LGSDZ Nerd Font")))) + (setq doom-font (font-spec :family font-family :size 14) + doom-big-font (font-spec :family font-family :size 24) + doom-big-font-increment 5 + doom-variable-pitch-font (font-spec :family "DejaVu Sans") + doom-unicode-font (font-spec :family font-family))) + +(require 's) + +(undefine-key! :keymaps 'doom-leader-map "/") + +(load! "utils") +(load! "company-sql") +(load! "show-matching-paren") +(load! "irc") +(load! "github-org") +(load! "org-gcal") +(load! "grid") +(load! "nix") +(load! "email") +(load! "cpp") +(load! "lisp") +(load! "clojure") +(load! "rust") +(load! "terraform") + +(require 'tvl) + +(add-hook! elixir-mode + (require 'flycheck-credo) + (setq flycheck-elixir-credo-strict t) + (flycheck-credo-setup) + + (require 'flycheck-mix) (flycheck-mix-setup) + + (require 'flycheck-dialyxir) (flycheck-dialyxir-setup) + + (flycheck-mode)) + +(setq +solarized-s-base03 "#002b36" + +solarized-s-base02 "#073642" + ;; emphasized content + +solarized-s-base01 "#586e75" + ;; primary content + +solarized-s-base00 "#657b83" + +solarized-s-base0 "#839496" + ;; comments + +solarized-s-base1 "#93a1a1" + ;; background highlight light + +solarized-s-base2 "#eee8d5" + ;; background light + +solarized-s-base3 "#fdf6e3" + + ;; Solarized accented colors + +solarized-yellow "#b58900" + +solarized-orange "#cb4b16" + +solarized-red "#dc322f" + +solarized-magenta "#d33682" + +solarized-violet "#6c71c4" + +solarized-blue "#268bd2" + +solarized-cyan "#2aa198" + +solarized-green "#859900" + + ;; Darker and lighter accented colors + ;; Only use these in exceptional circumstances! + +solarized-yellow-d "#7B6000" + +solarized-yellow-l "#DEB542" + +solarized-orange-d "#8B2C02" + +solarized-orange-l "#F2804F" + +solarized-red-d "#990A1B" + +solarized-red-l "#FF6E64" + +solarized-magenta-d "#93115C" + +solarized-magenta-l "#F771AC" + +solarized-violet-d "#3F4D91" + +solarized-violet-l "#9EA0E5" + +solarized-blue-d "#00629D" + +solarized-blue-l "#69B7F0" + +solarized-cyan-d "#00736F" + +solarized-cyan-l "#69CABF" + +solarized-green-d "#546E00" + +solarized-green-l "#B4C342") + +(defcustom theme-overrides nil + "Association list of override faces to set for different custom themes.") + +(defadvice load-theme (after theme-set-overrides activate) + (dolist (theme-settings theme-overrides) + (let ((theme (car theme-settings)) + (faces (cadr theme-settings))) + (if (member theme custom-enabled-themes) + (progn + (dolist (face faces) + (custom-theme-set-faces theme face))))))) + +(defun alist-set (alist-symbol key value) + "Set VALUE of a KEY in ALIST-SYMBOL." + (set alist-symbol (cons (list key value) (assq-delete-all key (eval alist-symbol))))) + +(comment + (custom-theme-set-faces 'grfn-solarized-light + `(font-lock-doc-face + ((t (:foreground ,+solarized-s-base1))))) + ++solarized-s-base1 +(custom-theme-) + (custom-face-get-current-spec 'font-lock-doc-face) + + ) + +(alist-set 'theme-overrides 'grfn-solarized-light + `((font-lock-doc-face ((t (:foreground ,+solarized-s-base1)))) + (font-lock-preprocessor-face ((t (:foreground ,+solarized-red)))) + (font-lock-keyword-face ((t (:foreground ,+solarized-green :bold nil)))) + (font-lock-builtin-face ((t (:foreground ,+solarized-s-base01 + :bold t)))) + + (elixir-attribute-face ((t (:foreground ,+solarized-blue)))) + (elixir-atom-face ((t (:foreground ,+solarized-cyan)))) + (linum ((t (:background ,+solarized-s-base2 :foreground ,+solarized-s-base1)))) + (line-number ((t (:background ,+solarized-s-base2 :foreground ,+solarized-s-base1)))) + (line-number-current-line ((t (:background ,+solarized-s-base2 :foreground ,+solarized-s-base1)))) + + (haskell-operator-face ((t (:foreground ,+solarized-green)))) + (haskell-keyword-face ((t (:foreground ,+solarized-cyan)))) + + (org-drawer ((t (:foreground ,+solarized-s-base1 + :bold t)))))) + +(setq solarized-use-variable-pitch nil + solarized-scale-org-headlines nil + solarized-use-less-bold t) + +(add-to-list 'custom-theme-load-path "~/.doom.d/themes") +(load-theme 'grfn-solarized-light t) + +(defface haskell-import-face `((t (:foreground ,+solarized-magenta))) "") + +(setq doom-theme 'grfn-solarized-light) +; (setq doom-theme 'doom-solarized-light) + +(add-hook! doom-post-init + (set-face-attribute 'bold nil :weight 'ultra-light) + (set-face-bold 'bold nil) + (enable-theme 'grfn-solarized-light)) + +(defun rx-words (&rest words) + (rx-to-string + `(and symbol-start (group (or ,@words)) symbol-end))) + +(font-lock-add-keywords + 'elixir-mode + `((,(rx-words "def" + "defp" + "test" + "describe" + "property" + "defrecord" + "defmodule" + "defstruct" + "defdelegate" + "defprotocol" + "defimpl" + "use" + "import" + "alias" + "require" + "assert" + "refute" + "assert_raise") + . + 'font-lock-preprocessor-face))) + +(font-lock-add-keywords + 'elixir-mode + `((,(rx-words "def" + "defp" + "test" + "describe" + "property" + "defrecord" + "defmodule" + "defstruct" + "defdelegate" + "use" + "import" + "alias" + "require" + "assert" + "refute" + "assert_raise") + . + 'font-lock-preprocessor-face))) + +(font-lock-add-keywords + 'haskell-mode + `((,(rx-words "import") . 'haskell-import-face))) + +;; (font-lock-add-keywords +;; 'haskell-mode +;; `((,(rx "-- |") . 'haskell-keyword-face))) + + +;; (load-file (let ((coding-system-for-read 'utf-8)) +;; (shell-command-to-string "agda-mode locate"))) + +(defvar +grfn-dir (file-name-directory load-file-name)) +(defvar +grfn-snippets-dir (expand-file-name "snippets/" +grfn-dir)) + +;; +(load! "+bindings") +(load! "+commands") +(load! "cpp") + + +(add-to-list 'load-path "/home/grfn/code/org-tracker") +(require 'org-tracker) +(use-package! org-tracker + :hook (org-mode . org-tracker-mode) + :config + (setq org-tracker-state-alist '(("INBOX" . "Inbox") + ("BACKLOG" . "Backlog") + ("TODO" . "Selected for Development") + ("ACTIVE" . "In Progress") + ("PR" . "Code Review") + ("DONE" . "Done")) + org-tracker-username "griffin@readyset.io" + org-tracker-claim-ticket-on-status-update '("ACTIVE" "PR" "DONE") + org-tracker-create-stories-with-labels 'existing) + + (defun org-tracker-headlines-from-assigned-to-me (level) + (interactive "*nLevel: ") + (funcall-interactively + #'org-tracker-headlines-from-search + level + "assignee = currentUser() and statusCategory = 2"))) + +(load! "+private") + +(require 'dash) + +(use-package! predd) + + +;; +;; Global config +;; + +(setq doom-modeline-buffer-file-name-style 'relative-to-project + doom-modeline-modal-icon nil + doom-modeline-github t) + +;; +;; Modules +;; + +(after! smartparens + ;; Auto-close more conservatively and expand braces on RET + (let ((unless-list '(sp-point-before-word-p + sp-point-after-word-p + sp-point-before-same-p))) + (sp-pair "'" nil :unless unless-list) + (sp-pair "\"" nil :unless unless-list)) + (sp-pair "{" nil :post-handlers '(("||\n[i]" "RET") ("| " " ")) + :unless '(sp-point-before-word-p sp-point-before-same-p)) + (sp-pair "(" nil :post-handlers '(("||\n[i]" "RET") ("| " " ")) + :unless '(sp-point-before-word-p sp-point-before-same-p)) + (sp-pair "[" nil :post-handlers '(("| " " ")) + :unless '(sp-point-before-word-p sp-point-before-same-p))) + +;; feature/snippets +(after! yasnippet + ;; Don't use default snippets, use mine. + (setq yas-snippet-dirs + (append (list '+grfn-snippets-dir) + (delq 'yas-installed-snippets-dir yas-snippet-dirs)))) + +(after! company + (setq company-idle-delay 0.2 + company-minimum-prefix-length 1)) + +(setq doom-modeline-height 12) + + + +;; Should really figure out which of these is correct, eventually + +(setq +solarized-s-base03 "#002b36" + +solarized-s-base02 "#073642" + ;; emphasized content + +solarized-s-base01 "#586e75" + ;; primary content + +solarized-s-base00 "#657b83" + +solarized-s-base0 "#839496" + ;; comments + +solarized-s-base1 "#93a1a1" + ;; background highlight light + +solarized-s-base2 "#eee8d5" + ;; background light + +solarized-s-base3 "#fdf6e3" + + ;; Solarized accented colors + +solarized-yellow "#b58900" + +solarized-orange "#cb4b16" + +solarized-red "#dc322f" + +solarized-magenta "#d33682" + +solarized-violet "#6c71c4" + +solarized-blue "#268bd2" + +solarized-cyan "#2aa198" + +solarized-green "#859900" + + ;; Darker and lighter accented colors + ;; Only use these in exceptional circumstances! + +solarized-yellow-d "#7B6000" + +solarized-yellow-l "#DEB542" + +solarized-orange-d "#8B2C02" + +solarized-orange-l "#F2804F" + +solarized-red-d "#990A1B" + +solarized-red-l "#FF6E64" + +solarized-magenta-d "#93115C" + +solarized-magenta-l "#F771AC" + +solarized-violet-d "#3F4D91" + +solarized-violet-l "#9EA0E5" + +solarized-blue-d "#00629D" + +solarized-blue-l "#69B7F0" + +solarized-cyan-d "#00736F" + +solarized-cyan-l "#69CABF" + +solarized-green-d "#546E00" + +solarized-green-l "#B4C342") + +(set-cursor-color +solarized-s-base02) + +(after! doom-theme + (set-face-foreground 'font-lock-doc-face +solarized-s-base1) + (set-face-foreground 'org-block +solarized-s-base00) + (set-face-foreground 'slack-message-output-header +solarized-s-base01) + (set-face-attribute 'slack-message-output-header nil :underline nil) + (set-face-attribute 'slack-message-output-text nil :height 1.0) + ) + +(after! solarized-theme + (set-face-foreground 'font-lock-doc-face +solarized-s-base1) + (set-face-foreground 'org-block +solarized-s-base00) + + (set-face-foreground 'slack-message-output-header +solarized-s-base01) + (set-face-attribute 'slack-message-output-header nil :underline nil) + (set-face-attribute 'slack-message-output-text nil :height 1.0) + ) + +(after! evil + (setq evil-shift-width 2)) + +(after! org + (load! "org-query") + (load! "org-config")) + +(after! magit + (setq git-commit-summary-max-length 50)) + +(after! ivy + ;; (setq ivy-re-builders-alist + ;; '((t . ivy--regex-fuzzy))) + ) + +(add-hook 'before-save-hook 'delete-trailing-whitespace) + +(after! paxedit + (add-hook! emacs-lisp-mode #'paxedit-mode) + (add-hook! clojure-mode #'paxedit-mode) + (add-hook! common-lisp-mode #'paxedit-mode)) + +(require 'haskell) + +(let ((m-symbols + '(("`mappend`" . "โ") + ("<>" . "โ") + ("`elem`" . "โ") + ("`notElem`" . "โ")))) + (dolist (item m-symbols) (add-to-list 'haskell-font-lock-symbols-alist item))) + +(setq haskell-font-lock-symbols t) + + +(add-hook! haskell-mode + ;; (intero-mode) + (lsp-mode) + ;; (flycheck-add-next-checker + ;; 'intero + ;; 'haskell-hlint) + (set-fill-column 80) + (setq evil-shift-width 2)) + +(auth-source-pass-enable) + +(require 'fill-column-indicator) +;;; * Column Marker +(defun sanityinc/fci-enabled-p () (symbol-value 'fci-mode)) + +(defvar sanityinc/fci-mode-suppressed nil) +(make-variable-buffer-local 'sanityinc/fci-mode-suppressed) + +(defadvice popup-create (before suppress-fci-mode activate) + "Suspend fci-mode while popups are visible" + (let ((fci-enabled (sanityinc/fci-enabled-p))) + (when fci-enabled + (setq sanityinc/fci-mode-suppressed fci-enabled) + (turn-off-fci-mode)))) + +(defadvice popup-delete (after restore-fci-mode activate) + "Restore fci-mode when all popups have closed" + (when (and sanityinc/fci-mode-suppressed + (null popup-instances)) + (setq sanityinc/fci-mode-suppressed nil) + (turn-on-fci-mode))) + + +;;; Javascript + +(require 'smartparens) + +(setq js-indent-level 2) + +(require 'prettier-js) +(after! prettier-js + (add-hook! rjsx-mode #'prettier-js-mode) + (add-hook! js2-mode #'prettier-js-mode) + (add-hook! json-mode #'prettier-js-mode) + (add-hook! css-mode #'prettier-js-mode)) + +(remove-hook 'js2-mode-hook 'tide-setup t) + +;; Set this to the mode you use, I use rjsx-mode +(add-hook 'rjsx-mode-hook #'flow/set-flow-executable t) + + +;; Auto-format Haskell on save, with a combination of hindent + brittany + +; (define-minor-mode brittany-haskell-mode +; :init-value nil +; :group 'haskell +; :lighter "Brittany-Haskell" +; :keymap '() +; ) + + +(require 'alert) +(setq alert-default-style 'libnotify) + +;; (setq slack-buffer-function #'switch-to-buffer) + +(setq projectile-test-suffix-function + (lambda (project-type) + (case project-type + ('haskell-stack "Test") + ('npm ".test") + (otherwise (projectile-test-suffix project-type))))) + +(setq projectile-create-missing-test-files 't) + +(after! magit + (map! :map magit-mode-map + ;; :n "] ]" #'magit-section-forward + ;; :n "[ [" #'magit-section-backward + ) + + (define-suffix-command magit-commit-wip () + (interactive) + (magit-commit-create '("-m" "wip"))) + + (transient-append-suffix + #'magit-commit + ["c"] + (list "W" "Commit WIP" #'magit-commit-wip)) + + (define-suffix-command magit-reset-head-back () + (interactive) + (magit-reset-mixed "HEAD~")) + + (define-suffix-command magit-reset-head-previous () + (interactive) + (magit-reset-mixed "HEAD@{1}")) + + (transient-append-suffix + #'magit-reset + ["f"] + (list "b" "Reset HEAD~" #'magit-reset-head-back)) + (transient-append-suffix + #'magit-reset + ["f"] + (list "o" "Reset HEAD@{1}" #'magit-reset-head-previous)) + + (defun magit-read-org-tracker-branch-name () + (when-let ((issue-id (org-tracker-clocked-in-issue-id))) + (let ((desc + (magit-read-string-ns + (format "Issue description (to go after gs/%s/)" + issue-id)))) + (format "gs/%s/%s" issue-id desc)))) + + (defun magit-read-org-tracker-branch-args () + (if-let ((issue-id (org-tracker-clocked-in-issue-id))) + (let ((start-point (magit-read-starting-point + "Create and checkout branch for Tracker issue" + nil + "origin/master"))) + (if (magit-rev-verify start-point) + (when-let ((desc (magit-read-org-tracker-branch-name))) + (list desc start-point)) + (user-error "Not a valid starting point: %s" choice))) + (user-error "No currently clocked-in tracker issue"))) + + (transient-define-suffix magit-checkout-org-tracker-branch (branch start-point) + (interactive (magit-read-org-tracker-branch-args)) + (magit-branch-and-checkout branch start-point)) + + (transient-define-suffix magit-rename-org-tracker-branch (old new) + (interactive + (let ((branch (magit-read-local-branch "Rename branch"))) + (list branch (magit-read-org-tracker-branch-name)))) + (when (and old new) + (magit-branch-rename old new))) + + (transient-append-suffix + #'magit-branch + ["c"] + (list "C" "Checkout Tracker branch" #'magit-checkout-org-tracker-branch)) + (transient-append-suffix + #'magit-branch + ["c"] + (list "M" "Rename branch to Tracker ticket" #'magit-rename-org-tracker-branch))) + +;; (defun grfn/split-window-more-sensibly (&optional window) +;; (let ((window (or window (selected-window)))) +;; (or (and (window-splittable-p window) +;; ;; Split window vertically. +;; (with-selected-window window +;; (split-window-right))) +;; (and (window-splittable-p window t) +;; ;; Split window horizontally. +;; (with-selected-window window +;; (split-window-right))) +;; (and (eq window (frame-root-window (window-frame window))) +;; (not (window-minibuffer-p window)) +;; ;; If WINDOW is the only window on its frame and is not the +;; ;; minibuffer window, try to split it vertically disregarding +;; ;; the value of `split-height-threshold'. +;; (let ((split-height-threshold 0)) +;; (when (window-splittable-p window) +;; (with-selected-window window +;; (split-window-below)))))))) + +(use-package! lsp-mode + :after (:any haskell-mode) + :config + (setq lsp-response-timeout 60) + :hook + (haskell-mode . lsp-mode)) + +(use-package! lsp-ui + :after lsp-mode + :config + (defun +grfn/lsp-ui-doc-frame-hook (frame window) + (set-frame-font (if doom-big-font-mode doom-big-font doom-font) + nil (list frame))) + (setq lsp-ui-flycheck-enable t + lsp-ui-doc-header nil + lsp-ui-doc-position 'top + lsp-ui-doc-alignment 'window + lsp-ui-doc-frame-hook '+grfn/lsp-ui-doc-frame-hook + lsp-ui-doc-max-width 150 + lsp-ui-doc-max-height 13) + (setq imenu-auto-rescan t) + (set-face-background 'lsp-ui-doc-background +solarized-s-base2) + (set-face-background 'lsp-face-highlight-read +solarized-s-base2) + (set-face-background 'lsp-face-highlight-write +solarized-s-base2) + :hook + (lsp-mode . lsp-ui-mode) + (lsp-ui-mode . flycheck-mode)) + +(use-package! company-lsp + :after (lsp-mode lsp-ui) + :config + (add-to-list #'company-backends #'company-lsp) + (setq company-lsp-async t)) + +(defun +grfn/haskell-mode-setup () + (interactive) + (flymake-mode -1) + (add-to-list 'flycheck-disabled-checkers 'haskell-ghc) + + (flycheck-remove-next-checker 'lsp 'haskell-ghc) + (flycheck-add-next-checker 'lsp '(warning . haskell-hlint)) + + ;; If thereโs a 'hie.sh' defined locally by a project + ;; (e.g. to run HIE in a nix-shell), use itโฆ + (when-let ((project-dir (locate-dominating-file default-directory "hie.sh"))) + (cl-flet + ((which (cmd) + (s-trim + (shell-command-to-string + (concat + "nix-shell " + (expand-file-name "shell.nix" project-dir) + " --run \"which " cmd "\" 2>/dev/null"))))) + (setq-local + lsp-haskell-process-path-hie (expand-file-name "hie.sh" project-dir) + haskell-hoogle-command (which "hoogle")))) + ;; โฆ and only then setup the LSP. + (lsp)) + +(defun never-flymake-mode (orig &rest args) + (when (and (bound-and-true-p flymake-mode)) + (funcall orig 0) + (message "disabled flymake-mode"))) +(advice-add #'flymake-mode :around #'never-flymake-mode) + +(defun +grfn/wrap-lsp-haskell-process (argv) + (let* ((project-dir (locate-dominating-file + (buffer-file-name) + "hie.yaml")) + (shell-dot-nix (expand-file-name "shell.nix" project-dir))) + ;; (when (string-equal default-directory "/home/grfn/code/depot") + ;; (debug)) + (message "%s %s %s %s" + (buffer-file-name) + default-directory + project-dir + shell-dot-nix) + (if (file-exists-p shell-dot-nix) + `("bash" "-c" + ,(format "cd %s && nix-shell %s --run '%s'" + project-dir + shell-dot-nix + (s-join " " argv))) + argv))) + +(use-package! lsp-haskell + :after (lsp-mode lsp-ui haskell-mode) + ;; :hook + ;; (haskell-mode . lsp-haskell-enable) + :config + (setq lsp-haskell-process-path-hie "haskell-language-server-wrapper" + lsp-haskell-process-args-hie + '("-d" "-l" "/tmp/hie.log" "+RTS" "-M4G" "-H1G" "-K4G" "-A16M" "-RTS") + lsp-haskell-process-wrapper-function + #'+grfn/wrap-lsp-haskell-process) + (add-hook 'haskell-mode-hook #'+grfn/haskell-mode-setup 't)) + +(use-package! lsp-imenu + :after (lsp-mode lsp-ui) + :hook + (lsp-after-open . lsp-enable-imenu)) + +;; (use-package! counsel-etags +;; :ensure t +;; :init +;; (add-hook 'haskell-mode-hook +;; (lambda () +;; (add-hook 'after-save-hook +;; 'counsel-etags-virtual-update-tags 'append 'local))) +;; :config +;; (setq counsel-etags-update-interval 60) +;; ;; (push "build" counsel-etags-ignore-directories) +;; ) + +;; (use-package! evil-magit +;; :after (magit)) + +(use-package! writeroom-mode) + +(use-package! graphql-mode) + + +(require 'whitespace) +(setq whitespace-style '(face lines-tail)) +(global-whitespace-mode t) +(add-hook 'org-mode-hook (lambda () (whitespace-mode -1)) t) + +(set-face-foreground 'whitespace-line +solarized-red) +(set-face-attribute 'whitespace-line nil :underline 't) + +;; (set-face-background 'ivy-posframe +solarized-s-base3) +;; (set-face-foreground 'ivy-posframe +solarized-s-base01) + +(let ((base03 "#002b36") + (base02 "#073642") + (base01 "#586e75") + (base00 "#657b83") + (base0 "#839496") + (base1 "#93a1a1") + (base2 "#eee8d5") + (base3 "#fdf6e3") + (yellow "#b58900") + (orange "#cb4b16") + (red "#dc322f") + (magenta "#d33682") + (violet "#6c71c4") + (blue "#268bd2") + (cyan "#2aa198") + (green "#859900")) + (custom-set-faces + `(agda2-highlight-keyword-face ((t (:foreground ,green)))) + `(agda2-highlight-string-face ((t (:foreground ,cyan)))) + `(agda2-highlight-number-face ((t (:foreground ,violet)))) + `(agda2-highlight-symbol-face ((((background ,base3)) (:foreground ,base01)))) + `(agda2-highlight-primitive-type-face ((t (:foreground ,blue)))) + `(agda2-highlight-bound-variable-face ((t nil))) + `(agda2-highlight-inductive-constructor-face ((t (:foreground ,green)))) + `(agda2-highlight-coinductive-constructor-face ((t (:foreground ,yellow)))) + `(agda2-highlight-datatype-face ((t (:foreground ,blue)))) + `(agda2-highlight-field-face ((t (:foreground ,red)))) + `(agda2-highlight-function-face ((t (:foreground ,blue)))) + `(agda2-highlight-module-face ((t (:foreground ,yellow)))) + `(agda2-highlight-postulate-face ((t (:foreground ,blue)))) + `(agda2-highlight-primitive-face ((t (:foreground ,blue)))) + `(agda2-highlight-record-face ((t (:foreground ,blue)))) + `(agda2-highlight-dotted-face ((t nil))) + `(agda2-highlight-operator-face ((t nil))) + `(agda2-highlight-error-face ((t (:foreground ,red :underline t)))) + `(agda2-highlight-unsolved-meta-face ((t (:background ,base2)))) + `(agda2-highlight-unsolved-constraint-face ((t (:background ,base2)))) + `(agda2-highlight-termination-problem-face ((t (:background ,orange :foreground ,base03)))) + `(agda2-highlight-incomplete-pattern-face ((t (:background ,orange :foreground ,base03)))) + `(agda2-highlight-typechecks-face ((t (:background ,cyan :foreground ,base03)))))) + + +(after! cider + (setq cider-prompt-for-symbol nil + cider-font-lock-dynamically 't + cider-save-file-on-load 't) + ) + +(defun +org-clocked-in-element () + (when-let ((item (car org-clock-history))) + (save-mark-and-excursion + (with-current-buffer (marker-buffer item) + (goto-char (marker-position item)) + (org-element-at-point))))) + +(comment + (setq elt (+org-clocked-in-item)) + + (eq 'headline (car elt)) + (plist-get (cadr elt) :raw-value) + ) + +(defun +org-headline-title (headline) + (when (eq 'headline (car elt)) + (plist-get (cadr elt) :raw-value))) + +(setq +ligatures-extra-symbols + (append +ligatures-extra-symbols + '(:equal "โก" + :not-equal "โ " + :is "โฃ" + :isnt "โข" + :lte "โค" + :gte "โฅ" + :subseteq "โ" + ))) + +(after! python + (set-pretty-symbols! 'python-mode :merge t + :equal "==" + :not-equal "!=" + :lte "<=" + :gte ">=" + :is "is" + :isnt "is not" + :subseteq "issubset" + + ;; doom builtins + + ;; Functional + :def "def" + :lambda "lambda" + ;; Types + :null "None" + :true "True" :false "False" + :int "int" :str "str" + :float "float" + :bool "bool" + :tuple "tuple" + ;; Flow + :not "not" + :in "in" :not-in "not in" + :and "and" :or "or" + :for "for" + :return "return" :yield "yield")) + +(use-package! sqlup-mode + :hook + (sql-mode-hook . sqlup-mode) + (sql-interactive-mode-hook . sqlup-mode)) + +(use-package! emacsql) +(use-package! emacsql-psql + :after (emacsql)) + +(use-package! pyimport + :after (python)) + +(use-package! blacken + :after (python) + :init + (add-hook #'python-mode-hook #'blacken-mode) + :config + (setq blacken-only-if-project-is-blackened t + blacken-allow-py36 t + blacken-line-length 100)) + +(after! python + (defun +python-setup () + (setq-local fill-column 100 + whitespace-line-column 100 + flycheck-disabled-checkers '(python-flake8) + flycheck-checker 'python-pylint)) + + (add-hook #'python-mode-hook #'+python-setup) + (add-hook #'python-mode-hook #'lsp) + (remove-hook #'python-mode-hook #'pipenv-mode)) + +; (use-package! w3m +; :config +; (setq browse-url-browser-function +; `(("^https://app.clubhouse.io.*" . browse-url-firefox) +; ("^https://github.com.*" . browse-url-firefox) +; (".*" . browse-url-firefox)))) + +(use-package! ob-http + :config + (add-to-list 'org-babel-load-languages '(http . t))) + +;; (use-package! ob-ipython +;; :after (pyimport) +;; :config +;; (add-to-list 'org-babel-load-languages '(ipython . t)) +;; (setq ob-ipython-command + ;; "/home/griffin/code/urb/ciml-video-classifier/bin/jupyter")) + +(use-package! counsel-spotify) + +(after! counsel + (map! [remap counsel-org-capture] #'org-capture + [remap org-capture] #'org-capture)) + +(use-package! evil-snipe :disabled t) +(evil-snipe-mode -1) + +(use-package! rainbow-mode) + +(use-package! org-alert + :disabled t + :config + (org-alert-enable) + (setq alert-default-style 'libnotify + org-alert-headline-title "org")) + +(use-package! ob-async) + +(use-package! org-recent-headings + :config + (map! :n "SPC n r" #'org-recent-headings-ivy)) + +(use-package! org-sticky-header + :after (org) + :hook (org-mode-hook . org-sticky-header-mode) + :config + (setq-default org-sticky-header-heading-star "โ")) + +(enable-theme 'grfn-solarized-light) + +;;; this needs to be *after the theme*, or else I get no agenda items. +;;; whuuu?? +(load! "org-config") + + +;;; word-char +(add-hook! prog-mode + (modify-syntax-entry ?_ "w")) + +(add-hook! lisp-mode + (modify-syntax-entry ?- "w")) + +(after! flycheck + (put 'flycheck-python-pylint-executable 'safe-local-variable (lambda (_) t)) + (setq flycheck-error-list-minimum-level 'warn + flycheck-navigation-minimum-level 'warn)) + +(defvar alembic-command "alembic" + "Command to execute when running alembic") + +(defvar alembic-dir-fun (lambda () default-directory) + "Reference to a function whose return value will be used as the directory to + run Alembic in") + +(put 'alembic-command 'safe-local-variable (lambda (_) t)) +(put 'alembic-dir-fun 'safe-local-variable (lambda (_) t)) + +(defun make-alembic-command (args) + (if (functionp alembic-command) + (funcall alembic-command args) + (concat alembic-command " " args))) + +(defun +grfn/extract-alembic-migration-name (output) + (unless (string-match (rx (0+ anything) "Generating " + (group (one-or-more (not (syntax whitespace)))) + " ..." (one-or-more (syntax whitespace)) "done" + (0+ anything)) + output) + (user-error "Error: %s" output)) + (match-string-no-properties 1 output)) + +(defun -run-alembic (args) + (let* ((default-directory (funcall alembic-dir-fun)) + (command (make-alembic-command args)) + ;; (format "nix-shell --run 'alembic %s'" args) + ;; (format "%s %s" alembic-command args) + (res + (with-temp-buffer + (cons + (shell-command command t) + (s-replace-regexp + "^.*Nix search path entry.*$" "" + (buffer-string))))) + (exit-code (car res)) + (out (cdr res))) + ;; (if (= 0 exit-code) + ;; out + ;; (error "Error running %s: %s" command out)) + out + )) + +(comment + --exit-code + --bs + ) + +(defun run-alembic (args) + (interactive "sAlembic command: ") + (message "%s" (-run-alembic args))) + +(defun generate-alembic-migration (msg &rest args) + (interactive "sMessage: ") + (-> + (format "revision %s -m \"%s\"" + (s-join " " args) + msg) + (-run-alembic) + (+grfn/extract-alembic-migration-name) + (find-file-other-window))) + +(cl-defun alembic-upgrade (&optional revision &key namespace) + (interactive "sRevision: ") + (let ((default-directory (funcall alembic-dir-fun))) + (run-alembic (format "%s upgrade %s" + (if namespace (concat "-n " namespace) "") + (or revision "head"))))) + +(defun alembic-downgrade (revision) + (interactive "sRevision: ") + (let ((default-directory (funcall alembic-dir-fun))) + (run-alembic (format "downgrade %s" (or revision "head"))))) + +(use-package! gnuplot) +(use-package! gnuplot-mode :after gnuplot) +(use-package! string-inflection) + +(after! anaconda-mode + ;; (set-company-backend! 'anaconda-mode #'company-yasnippet) + ) + +;; (add-hook! python-mode +;; (capf)) + +(cl-defstruct pull-request url number title author repository) + +(defun grfn/num-inbox-items () + (length (org-elements-agenda-match "inbox" t))) + +(use-package! dhall-mode + :mode "\\.dhall\\'") + +(use-package! github-review + :after forge) + +(after! forge + (set-popup-rule! + "^\\*forge" + :size 0.75)) + +(defun grfn/org-add-db-connection-params () + (interactive) + (ivy-read + "DB to connect to: " + (-map (lambda (opts) + (propertize (symbol-name (car opts)) + 'header-args (cdr opts))) + db-connection-param-options) + :require-match t + :action + (lambda (opt) + (let ((header-args (get-text-property 0 'header-args opt))) + (org-set-property "header-args" header-args))))) + +(use-package! kubernetes + :commands (kubernetes-overview)) + +(use-package! k8s-mode + :hook (k8s-mode . yas-minor-mode)) + +(use-package! sx) + +;; (use-package! nix-update +;; :config +;; (map! (:map nix-mode-map +;; (:leader +;; :desc "Update fetcher" :nv #'nix-update-fetch)))) + + +(after! lsp-haskell + (lsp-register-client + (make-lsp--client + :new-connection (lsp-stdio-connection (lambda () (lsp-haskell--hie-command))) + :major-modes '(haskell-mode) + :server-id 'hie + ;; :multi-root t + ;; :initialization-options 'lsp-haskell--make-init-options + ) + ) + ) + +(solaire-global-mode -1) + +(use-package! wsd-mode) + +(use-package! metal-mercury-mode) +(use-package! flycheck-mercury + :after (metal-mercury-mode flycheck-mercury)) + +(use-package! direnv + :config (direnv-mode)) + +(after! erc + ;; (setq erc-autojoin-channels-alist '(("freenode.net" "#nixos" "#haskell" "##tvl"))) + ) + +(defun evil-disable-insert-state-bindings () + evil-disable-insert-state-bindings) + +;; (use-package! terraform-mode) +;; (use-package! company-terraform +;; :after terraform-mode +;; :config (company-terraform-init)) + +(use-package! znc + :config + (setq znc-servers + '(("znc.gws.fyi" 5000 t + ((freenode "glittershark" "Ompquy")))))) + +(use-package! jsonnet-mode + :config + (map! + (:map jsonnet-mode-map + (:n "g SPC" #'jsonnet-eval-buffer)))) + +(add-to-list 'safe-local-variable-values + '(truncate-lines . t)) + +(set-popup-rule! + "^\\*gud-" + :quit nil) diff --git a/users/grfn/emacs.d/cpp.el b/users/grfn/emacs.d/cpp.el new file mode 100644 index 000000000000..5b5dc8ead652 --- /dev/null +++ b/users/grfn/emacs.d/cpp.el @@ -0,0 +1,39 @@ +;;; -*- lexical-binding: t; -*- + + +(load! "google-c-style") + +(after! flycheck + (add-to-list 'flycheck-disabled-checkers 'c/c++-gcc) + (add-to-list 'flycheck-disabled-checkers 'c/c++-clang)) + +(defun +grfn/cpp-setup () + (when (s-starts-with? + "/home/grfn/code/depot/third_party/nix" + (buffer-file-name)) + (setq lsp-clients-clangd-executable "/home/grfn/code/depot/users/grfn/emacs.d/nix-clangd.sh" + lsp-clients-clangd-args nil) + (google-set-c-style) + (lsp) + (add-to-list 'flycheck-disabled-checkers 'c/c++-gcc) + (add-to-list 'flycheck-disabled-checkers 'c/c++-clang))) + +(add-hook 'c++-mode-hook #'+grfn/cpp-setup) + +(use-package! protobuf-mode) + +(use-package! clang-format+ + :config + (add-hook 'c-mode-common-hook #'clang-format+-mode)) + +(map! + (:map c++-mode-map + :leader + (:n "/ i" #'counsel-semantic-or-imenu))) + +(comment + (setq + lsp-clients-clangd-executable + "/home/grfn/code/depot/third_party/nix/clangd.sh" + lsp-clients-clangd-args nil) + ) diff --git a/users/grfn/emacs.d/email.el b/users/grfn/emacs.d/email.el new file mode 100644 index 000000000000..83076898b4dc --- /dev/null +++ b/users/grfn/emacs.d/email.el @@ -0,0 +1,42 @@ +;;; -*- lexical-binding: t; -*- + +(after! notmuch + (setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox tag:important not tag:trash" :key "i") + (:name "flagged" :query "tag:flagged" :key "f") + (:name "sent" :query "tag:sent" :key "s") + (:name "drafts" :query "tag:draft" :key "d") + + (:name "work" :query "tag:inbox and tag:important and path:work/**" + :key "w") + (:name "personal" :query "tag:inbox and tag:important and path:personal/**" + :key "p")) + message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-f-is-evil 't + message-sendmail-envelope-from 'header + message-sendmail-extra-arguments '("--read-envelope-from")) + + (add-hook! notmuch-message-mode-hook #'notmuch-company-setup)) + +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox tag:important not tag:trash" :key "i") + (:name "flagged" :query "tag:flagged" :key "f") + (:name "sent" :query "tag:sent" :key "s") + (:name "drafts" :query "tag:draft" :key "d") + + (:name "work" :query "tag:inbox and tag:important and path:work/**" + :key "w") + (:name "personal" :query "tag:inbox and tag:important and path:personal/**" + :key "p")) + message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-f-is-evil 't + message-sendmail-envelope-from 'header + message-sendmail-extra-arguments '("--read-envelope-from")) + +(set-popup-rule! "^\\*notmuch-saved-search-" + :ignore t) + +(set-popup-rule! (lambda (_ action) + (eq (car action) + 'display-buffer-same-window)) + :ignore t) diff --git a/users/grfn/emacs.d/github-org.el b/users/grfn/emacs.d/github-org.el new file mode 100644 index 000000000000..f4f9d2e37069 --- /dev/null +++ b/users/grfn/emacs.d/github-org.el @@ -0,0 +1,99 @@ +;;; -*- lexical-binding: t; -*- + +(require 'ghub) + +(defun grfn/alist->plist (alist) + (->> alist + (-mapcat (lambda (pair) + (list (intern (concat ":" (symbol-name (car pair)))) + (cdr pair)))))) + +;;; + +(cl-defstruct pull-request url number title author repository) + +(defun grfn/query-pulls (query) + (let ((resp (ghub-graphql "query reviewRequests($query: String!) { + reviewRequests: search( + type:ISSUE, + query: $query, + first: 100 + ) { + issueCount + nodes { + ... on PullRequest { + url + number + title + author { + login + ... on User { name } + } + repository { + name + owner { login } + } + } + } + } + }" `((query . ,query))))) + (->> resp + (alist-get 'data) + (alist-get 'reviewRequests) + (alist-get 'nodes) + (-map + (lambda (pr) + (apply + #'make-pull-request + (grfn/alist->plist pr))))))) + +(defun grfn/requested-changes ()) + +(defun grfn/pull-request->org-headline (format-string level pr) + (check-type format-string string) + (check-type level integer) + (check-type pr pull-request) + (s-format (concat (make-string level ?*) " " format-string) + 'aget + `((author . ,(or (->> pr (pull-request-author) (alist-get 'name)) + "no author")) + (owner . ,(->> pr (pull-request-repository) + (alist-get 'owner) + (alist-get 'login))) + (repo . ,(->> pr (pull-request-repository) (alist-get 'name))) + (pr-link . ,(org-make-link-string + (pull-request-url pr) + (pull-request-title pr))) + (today . ,(format-time-string "%Y-%m-%d %a"))))) + +(defun grfn/org-headlines-from-review-requests (level) + "Create org-mode headlines at LEVEL from all review-requested PRs on Github" + (interactive "*nLevel: ") + (let* ((prs (grfn/query-pulls + "is:open is:pr review-requested:glittershark archived:false")) + (text (mapconcat + (apply-partially + #'grfn/pull-request->org-headline + "TODO Review ${author}'s PR on ${owner}/${repo}: ${pr-link} :pr: +SCHEDULED: <${today}>" + level) prs "\n"))) + (save-mark-and-excursion + (insert text)) + (org-align-tags 't))) + +(defun grfn/org-headlines-from-requested-changes (level) + "Create org-mode headlines at LEVEL from all PRs with changes requested + on Github" + (interactive "*nLevel: ") + (let* ((prs (grfn/query-pulls + (concat "is:pr is:open author:glittershark archived:false " + "sort:updated-desc review:changes-requested"))) + (text (mapconcat + (apply-partially + #'grfn/pull-request->org-headline + "TODO Address review comments on ${pr-link} :pr: +SCHEDULED: <${today}>" + level) prs "\n"))) + (save-mark-and-excursion + (insert text)) + (org-align-tags 't))) diff --git a/users/grfn/emacs.d/google-c-style.el b/users/grfn/emacs.d/google-c-style.el new file mode 100644 index 000000000000..9bb12c61aae4 --- /dev/null +++ b/users/grfn/emacs.d/google-c-style.el @@ -0,0 +1,151 @@ +;;; google-c-style.el --- Google's C/C++ style for c-mode + +;; Keywords: c, tools + +;; google-c-style.el is Copyright (C) 2008 Google Inc. All Rights Reserved. +;; +;; It is free software; you can redistribute it and/or modify it under the +;; terms of either: +;; +;; a) the GNU General Public License as published by the Free Software +;; Foundation; either version 1, or (at your option) any later version, or +;; +;; b) the "Artistic License". + +;;; Commentary: + +;; Provides the google C/C++ coding style. You may wish to add +;; `google-set-c-style' to your `c-mode-common-hook' after requiring this +;; file. For example: +;; +;; (add-hook 'c-mode-common-hook 'google-set-c-style) +;; +;; If you want the RETURN key to go to the next line and space over +;; to the right place, add this to your .emacs right after the load-file: +;; +;; (add-hook 'c-mode-common-hook 'google-make-newline-indent) + +;;; Code: + +;; For some reason 1) c-backward-syntactic-ws is a macro and 2) under Emacs 22 +;; bytecode cannot call (unexpanded) macros at run time: +(eval-when-compile (require 'cc-defs)) + +;; Wrapper function needed for Emacs 21 and XEmacs (Emacs 22 offers the more +;; elegant solution of composing a list of lineup functions or quantities with +;; operators such as "add") +(defun google-c-lineup-expression-plus-4 (langelem) + "Indents to the beginning of the current C expression plus 4 spaces. + +This implements title \"Function Declarations and Definitions\" +of the Google C++ Style Guide for the case where the previous +line ends with an open parenthese. + +\"Current C expression\", as per the Google Style Guide and as +clarified by subsequent discussions, means the whole expression +regardless of the number of nested parentheses, but excluding +non-expression material such as \"if(\" and \"for(\" control +structures. + +Suitable for inclusion in `c-offsets-alist'." + (save-excursion + (back-to-indentation) + ;; Go to beginning of *previous* line: + (c-backward-syntactic-ws) + (back-to-indentation) + (cond + ;; We are making a reasonable assumption that if there is a control + ;; structure to indent past, it has to be at the beginning of the line. + ((looking-at "\\(\\(if\\|for\\|while\\)\\s *(\\)") + (goto-char (match-end 1))) + ;; For constructor initializer lists, the reference point for line-up is + ;; the token after the initial colon. + ((looking-at ":\\s *") + (goto-char (match-end 0)))) + (vector (+ 4 (current-column))))) + +;;;###autoload +(defconst google-c-style + `((c-recognize-knr-p . nil) + (c-enable-xemacs-performance-kludge-p . t) ; speed up indentation in XEmacs + (c-basic-offset . 2) + (indent-tabs-mode . nil) + (c-comment-only-line-offset . 0) + (c-hanging-braces-alist . ((defun-open after) + (defun-close before after) + (class-open after) + (class-close before after) + (inexpr-class-open after) + (inexpr-class-close before) + (namespace-open after) + (inline-open after) + (inline-close before after) + (block-open after) + (block-close . c-snug-do-while) + (extern-lang-open after) + (extern-lang-close after) + (statement-case-open after) + (substatement-open after))) + (c-hanging-colons-alist . ((case-label) + (label after) + (access-label after) + (member-init-intro before) + (inher-intro))) + (c-hanging-semi&comma-criteria + . (c-semi&comma-no-newlines-for-oneline-inliners + c-semi&comma-inside-parenlist + c-semi&comma-no-newlines-before-nonblanks)) + (c-indent-comments-syntactically-p . t) + (comment-column . 40) + (c-indent-comment-alist . ((other . (space . 2)))) + (c-cleanup-list . (brace-else-brace + brace-elseif-brace + brace-catch-brace + empty-defun-braces + defun-close-semi + list-close-comma + scope-operator)) + (c-offsets-alist . ((arglist-intro google-c-lineup-expression-plus-4) + (func-decl-cont . ++) + (member-init-intro . ++) + (inher-intro . ++) + (comment-intro . 0) + (arglist-close . c-lineup-arglist) + (topmost-intro . 0) + (block-open . 0) + (inline-open . 0) + (substatement-open . 0) + (statement-cont + . + (,(when (fboundp 'c-no-indent-after-java-annotations) + 'c-no-indent-after-java-annotations) + ,(when (fboundp 'c-lineup-assignments) + 'c-lineup-assignments) + ++)) + (label . /) + (case-label . +) + (statement-case-open . +) + (statement-case-intro . +) ; case w/o { + (access-label . /) + (innamespace . 0)))) + "Google C/C++ Programming Style.") + +;;;###autoload +(defun google-set-c-style () + "Set the current buffer's c-style to Google C/C++ Programming + Style. Meant to be added to `c-mode-common-hook'." + (interactive) + (make-local-variable 'c-tab-always-indent) + (setq c-tab-always-indent t) + (c-add-style "Google" google-c-style t)) + +;;;###autoload +(defun google-make-newline-indent () + "Sets up preferred newline behavior. Not set by default. Meant + to be added to `c-mode-common-hook'." + (interactive) + (define-key c-mode-base-map "\C-m" 'newline-and-indent) + (define-key c-mode-base-map [ret] 'newline-and-indent)) + +(provide 'google-c-style) +;;; google-c-style.el ends here diff --git a/users/grfn/emacs.d/grid.el b/users/grfn/emacs.d/grid.el new file mode 100644 index 000000000000..75776a38cd9d --- /dev/null +++ b/users/grfn/emacs.d/grid.el @@ -0,0 +1,128 @@ +;;; -*- lexical-binding: t; -*- + +(require 's) + +(defun grfn/all-match-groups (s) + (loop for n from 1 + for x = (match-string n s) + while x + collect x)) + +(defun projectile-grid-ff (path &optional ask) + "Call `find-file' function on PATH when it is not nil and the file exists. +If file does not exist and ASK in not nil it will ask user to proceed." + (if (or (and path (file-exists-p path)) + (and ask (yes-or-no-p + (s-lex-format + "File does not exists. Create a new buffer ${path} ?")))) + (find-file path))) + +(defun projectile-grid-goto-file (filepath &optional ask) + "Find FILEPATH after expanding root. ASK is passed straight to `projectile-grid-ff'." + (projectile-grid-ff (projectile-expand-root filepath) ask)) + +(defun projectile-grid-choices (ds) + "Uses `projectile-dir-files' function to find files in directories. +The DIRS is list of lists consisting of a directory path and regexp to filter files from that directory. +Optional third element can be present in the DS list. The third element will be a prefix to be placed before +the filename in the resulting choice. +Returns a hash table with keys being short names (choices) and values being relative paths to the files." + (loop with hash = (make-hash-table :test 'equal) + for (dir re prefix) in ds do + (loop for file in (projectile-dir-files (projectile-expand-root dir)) do + (when (string-match re file) + (puthash + (concat (or prefix "") + (s-join "/" (grfn/all-match-groups file))) + (concat dir file) + hash))) + finally return hash)) + +(defmacro projectile-grid-find-resource (prompt dirs &optional newfile-template) + "Presents files from DIRS with PROMPT to the user using `projectile-completing-read'. +If users chooses a non existant file and NEWFILE-TEMPLATE is not nil +it will use that variable to interpolate the name for the new file. +NEWFILE-TEMPLATE will be the argument for `s-lex-format'. +The bound variable is \"filename\"." + `(lexical-let ((choices (projectile-grid-choices ,dirs))) + (projectile-completing-read + ,prompt + (hash-table-keys choices) + :action + (lambda (c) + (let* ((filepath (gethash c choices)) + (filename c)) ;; so `s-lex-format' can interpolate FILENAME + (if filepath + (projectile-grid-goto-file filepath) + (when-let ((newfile-template ,newfile-template)) + (projectile-grid-goto-file + (funcall newfile-template filepath) + ;; (cond + ;; ((functionp newfile-template) (funcall newfile-template filepath)) + ;; ((stringp newfile-template) (s-lex-format newfile-template))) + t)))))))) + +(defun projectile-grid-find-model () + "Find a model." + (interactive) + (projectile-grid-find-resource + "model: " + '(("python/urbint_lib/models/" + "\\(.+\\)\\.py$") + ("python/urbint_lib/" + "\\(.+\\)/models/\\(.+\\).py$")) + (lambda (filename) + (pcase (s-split "/" filename) + (`(,model) + (s-lex-format "python/urbint_lib/models/${model}.py")) + (`(,app ,model) + (s-lex-format "python/urbint_lib/${app}/models/${model}.py")))))) + +(defun projectile-grid-find-repository () + "Find a repository." + (interactive) + (projectile-grid-find-resource + "repository: " + '(("python/urbint_lib/repositories/" + "\\(.+\\)\\.py$") + ("python/urbint_lib/" + "\\(.+\\)/repositories/\\(.+\\).py$")) + (lambda (filename) + (pcase (s-split "/" filename) + (`(,repository) + (s-lex-format "python/urbint_lib/repositories/${repository}.py")) + (`(,app ,repository) + (s-lex-format "python/urbint_lib/${app}/repositories/${repository}.py")))))) + +(defun projectile-grid-find-controller () + "Find a controller." + (interactive) + (projectile-grid-find-resource + "controller: " + '(("backend/src/grid/api/controllers/" + "\\(.+\\)\\.py$") + ("backend/src/grid/api/apps/" + "\\(.+\\)/controllers/\\(.+\\).py$")) + (lambda (filename) + (pcase (s-split "/" filename) + (`(,controller) + (s-lex-format "backend/src/grid/api/controllers/${controller}.py")) + (`(,app ,controller) + (s-lex-format "backend/src/grid/api/apps/${app}/controllers/${controller}.py")))))) + +(setq projectile-grid-mode-map + (let ((map (make-keymap))) + (map! + (:map map + (:leader + (:desc "Edit..." :prefix "e" + :desc "Model" :n "m" #'projectile-grid-find-model + :desc "Controller" :n "c" #'projectile-grid-find-controller + :desc "Repository" :n "r" #'projectile-grid-find-repository)))) + map)) + +(define-minor-mode projectile-grid-mode + "Minor mode for finding files in GRID" + :init-value nil + :lighter " GRID" + :keymap projectile-grid-mode-map) diff --git a/users/grfn/emacs.d/init.el b/users/grfn/emacs.d/init.el new file mode 100644 index 000000000000..563b455dff15 --- /dev/null +++ b/users/grfn/emacs.d/init.el @@ -0,0 +1,233 @@ +;;; -*- lexical-binding: t; -*- + +(doom! :completion + company ; the ultimate code completion backend + (ivy +fuzzy + +prescient) ; a search engine for love and life + + :ui + ;;deft ; notational velocity for Emacs + doom ; what makes DOOM look the way it does + ;doom-dashboard ; a nifty splash screen for Emacs + doom-quit ; DOOM quit-message prompts when you quit Emacs + ;fill-column ; a `fill-column' indicator + hl-todo ; highlight TODO/FIXME/NOTE tags + ;;indent-guides ; highlighted indent columns + modeline ; snazzy, Atom-inspired modeline, plus API + nav-flash ; blink the current line after jumping + ;;neotree ; a project drawer, like NERDTree for vim + ophints ; highlight the region an operation acts on + (popup ; tame sudden yet inevitable temporary windows + +all ; catch all popups that start with an asterix + +defaults) ; default popup rules + ligatures ; replace bits of code with pretty symbols + ;;tabbar ; FIXME an (incomplete) tab bar for Emacs + ;;treemacs ; a project drawer, like neotree but cooler + unicode ; extended unicode support for various languages + vc-gutter ; vcs diff in the fringe + vi-tilde-fringe ; fringe tildes to mark beyond EOB + window-select ; visually switch windows + workspaces ; tab emulation, persistence & separate workspaces + + :editor + (evil +everywhere); come to the dark side, we have cookies + file-templates ; auto-snippets for empty files + fold ; (nigh) universal code folding + ;;(format +onsave) ; automated prettiness + ;;lispy ; vim for lisp, for people who dont like vim + multiple-cursors ; editing in many places at once + ;;parinfer ; turn lisp into python, sort of + rotate-text ; cycle region at point between text candidates + snippets ; my elves. They type so I don't have to + word-wrap + + :emacs + (dired ; making dired pretty [functional] + ;;+ranger ; bringing the goodness of ranger to dired + ;;+icons ; colorful icons for dired-mode + ) + electric ; smarter, keyword-based electric-indent + ;;eshell ; a consistent, cross-platform shell (WIP) + ;;term ; terminals in Emacs + vc ; version-control and Emacs, sitting in a tree + (undo +tree) + + :tools + ;;ansible + ;;debugger ; FIXME stepping through code, to help you add bugs + ;;direnv + docker + ;;editorconfig ; let someone else argue about tabs vs spaces + ;; ein ; tame Jupyter notebooks with emacs + (eval +overlay) ; run code, run (also, repls) + gist ; interacting with github gists + (lookup ; helps you navigate your code and documentation + +docsets) ; ...or in Dash docsets locally + lsp + ;;macos ; MacOS-specific commands + magit ; a git porcelain for Emacs + make ; run make tasks from Emacs + pass ; password manager for nerds + pdf ; pdf enhancements + ;;prodigy ; FIXME managing external services & code builders + ;;rgb ; creating color strings + ;;terraform ; infrastructure as code + ;;tmux ; an API for interacting with tmux + ;;upload ; map local to remote projects via ssh/ftp + ;;wakatime + ;;vterm ; another terminals in Emacs + + :checkers + syntax ; tasing you for every semicolon you forget + ; spell ; tasing you for misspelling mispelling + + :lang + agda ; types of types of types of types... + ;;assembly ; assembly for fun or debugging + cc ; C/C++/Obj-C madness + clojure ; java with a lisp + common-lisp ; if you've seen one lisp, you've seen them all + ; coq ; proofs-as-programs + ;;crystal ; ruby at the speed of c + ;;csharp ; unity, .NET, and mono shenanigans + data ; config/data formats + erlang ; an elegant language for a more civilized age + elixir ; erlang done right + ;;elm ; care for a cup of TEA? + emacs-lisp ; drown in parentheses + ;;ess ; emacs speaks statistics + ;;go ; the hipster dialect + ;; (haskell +intero) ; a language that's lazier than I am + haskell ; a language that's lazier than I am + ;;hy ; readability of scheme w/ speed of python + ;; idris ; + ;;(java +meghanada) ; the poster child for carpal tunnel syndrome + javascript ; all(hope(abandon(ye(who(enter(here)))))) + julia ; a better, faster MATLAB + ;;kotlin ; a better, slicker Java(Script) + latex ; writing papers in Emacs has never been so fun + ;;ledger ; an accounting system in Emacs + lua ; one-based indices? one-based indices + markdown ; writing docs for people to ignore + ;;nim ; python + lisp at the speed of c + nix ; I hereby declare "nix geht mehr!" + ;;ocaml ; an objective camel + (org ; organize your plain life in plain text + +dragndrop ; drag & drop files/images into org buffers + +attach ; custom attachment system + +babel ; running code in org + +capture ; org-capture in and outside of Emacs + +export ; Exporting org to whatever you want + ;; +habit ; Keep track of your habits + +present ; Emacs for presentations + +pretty + +brain + +protocol) ; Support for org-protocol:// links + ;;perl ; write code no one else can comprehend + ;;php ; perl's insecure younger brother + ;;plantuml ; diagrams for confusing people more + purescript ; javascript, but functional + (python +lsp) ; beautiful is better than ugly + ;;qt ; the 'cutest' gui framework ever + racket ; a DSL for DSLs + rest ; Emacs as a REST client + ;;ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"} + rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap() + ;;scala ; java, but good + (sh +fish) ; she sells (ba|z|fi)sh shells on the C xor + ;;solidity ; do you need a blockchain? No. + ;;swift ; who asked for emoji variables? + ;;terra ; Earth and Moon in alignment for performance. + ;;web ; the tubes + ;;vala ; GObjective-C + + ;; Applications are complex and opinionated modules that transform Emacs + ;; toward a specific purpose. They may have additional dependencies and + ;; should be loaded late. + :app + ;;(email +gmail) ; emacs as an email client + irc ; how neckbeards socialize + ;;(rss +org) ; emacs as an RSS reader + twitter ; twitter client https://twitter.com/vnought + ;;(write ; emacs as a word processor (latex + org + markdown) + ;; +wordnut ; wordnet (wn) search + ;; +langtool) ; a proofreader (grammar/style check) for Emacs + + :email + ;; (mu4e +gmail) + notmuch + + :collab + ;;floobits ; peer programming for a price + ;;impatient-mode ; show off code over HTTP + + :config + ;; For literate config users. This will tangle+compile a config.org + ;; literate config in your `doom-private-dir' whenever it changes. + ;;literate + + ;; The default module sets reasonable defaults for Emacs. It also + ;; provides a Spacemacs-inspired keybinding scheme and a smartparens + ;; config. Use it as a reference for your own modules. + (default +bindings +smartparens)) +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(doom-big-font-mode nil) + '(flycheck-javascript-flow-args nil) + '(org-agenda-files + '("/home/griffin/notes/personal.org" "/home/griffin/notes/2020-01-27-data-pipeline-deploy-mismatch.org" "/home/griffin/notes/architecture.org" "/home/griffin/notes/cooking.org" "/home/griffin/notes/culture-survey.org" "/home/griffin/notes/dir-structure.org" "/home/griffin/notes/dnd.org" "/home/griffin/notes/inbox.org" "/home/griffin/notes/misc-todo.org" "/home/griffin/notes/nix-talk.org" "/home/griffin/notes/notes.org" "/home/griffin/notes/one-on-one.org" "/home/griffin/notes/work.org" "/home/griffin/notes/xanthous.org" "/home/griffin/notes/xgboost.org")) + '(safe-local-variable-values + '((intero-stack-yaml . "/home/griffin/code/mlem/stack.yaml") + (elisp-lint-indent-specs + (if-let* . 2) + (when-let* . 1) + (let* . defun) + (nrepl-dbind-response . 2) + (cider-save-marker . 1) + (cider-propertize-region . 1) + (cider-map-repls . 1) + (cider--jack-in . 1) + (cider--make-result-overlay . 1) + (insert-label . defun) + (insert-align-label . defun) + (insert-rect . defun) + (cl-defun . 2) + (with-parsed-tramp-file-name . 2) + (thread-first . 1) + (thread-last . 1)) + (checkdoc-package-keywords-flag) + (cider-jack-in-default . "shadow-cljs") + (projectile-project-root . "/home/griffin/code/urb/grid/backend/src") + (python-pytest-executable . "/home/griffin/code/urb/grid/backend/src/.venv/bin/pytest")))) +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(default ((((class color) (min-colors 89)) (:foreground "#657b83" :background "#fdf6e3")))) + '(agda2-highlight-bound-variable-face ((t nil))) + '(agda2-highlight-coinductive-constructor-face ((t (:foreground "#b58900")))) + '(agda2-highlight-datatype-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-dotted-face ((t nil))) + '(agda2-highlight-error-face ((t (:foreground "#dc322f" :underline t)))) + '(agda2-highlight-field-face ((t (:foreground "#dc322f")))) + '(agda2-highlight-function-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-incomplete-pattern-face ((t (:background "#cb4b16" :foreground "#002b36")))) + '(agda2-highlight-inductive-constructor-face ((t (:foreground "#859900")))) + '(agda2-highlight-keyword-face ((t (:foreground "#859900")))) + '(agda2-highlight-module-face ((t (:foreground "#b58900")))) + '(agda2-highlight-number-face ((t (:foreground "#6c71c4")))) + '(agda2-highlight-operator-face ((t nil))) + '(agda2-highlight-postulate-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-primitive-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-primitive-type-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-record-face ((t (:foreground "#268bd2")))) + '(agda2-highlight-string-face ((t (:foreground "#2aa198")))) + '(agda2-highlight-symbol-face ((((background "#fdf6e3")) (:foreground "#586e75")))) + '(agda2-highlight-termination-problem-face ((t (:background "#cb4b16" :foreground "#002b36")))) + '(agda2-highlight-typechecks-face ((t (:background "#2aa198" :foreground "#002b36")))) + '(agda2-highlight-unsolved-constraint-face ((t (:background "#eee8d5")))) + '(agda2-highlight-unsolved-meta-face ((t (:background "#eee8d5"))))) diff --git a/users/grfn/emacs.d/irc.el b/users/grfn/emacs.d/irc.el new file mode 100644 index 000000000000..117869599d83 --- /dev/null +++ b/users/grfn/emacs.d/irc.el @@ -0,0 +1,131 @@ +;;; -*- lexical-binding: t; -*- + +(require 'erc) +(require 'alert) + +(defvar irc-servers + '("hackint" + "libera")) + +(defun irc-connect (server) + (interactive + (list (ivy-read "Server: " irc-servers))) + (let ((pw (s-trim (shell-command-to-string + (format "pass irccloud/%s" server)))) + (gnutls-verify-error nil)) + (erc-tls :server "bnc.irccloud.com" + :port 6697 + :nick "grfn" + :password (concat "bnc@" + (s-trim (shell-command-to-string "hostname")) + ":" + pw)))) + + +(defgroup erc-alert nil + "Alert me using alert.el for important ERC messages" + :group 'erc) + +(defcustom erc-noise-regexp + "\\(Logging in:\\|Signing off\\|You're now away\\|Welcome back\\)" + "This regexp matches unwanted noise." + :type 'regexp + :group 'erc) + +(setq tvl-enabled? t) + +(defun disable-tvl-notifications () + (interactive) + (setq tvl-enabled? nil)) + +(defun enable-tvl-notifications () + (interactive) + (setq tvl-enabled? t)) + +(defun erc-alert-important-p (info) + (let ((message (plist-get info :message)) + (erc-message (-> info (plist-get :data) (plist-get :message))) + (erc-channel (-> info (plist-get :data) (plist-get :channel)))) + (and erc-message + (not (or (string-match "^\\** *Users on #" message) + (string-match erc-noise-regexp + message))) + (or (and tvl-enabled? + (string-equal erc-channel "#tvl")) + (string-match "grfn" message))))) + +(comment + last-info + erc-noise-regexp + (setq tvl-enabled? nil) + ) + +(defun my-erc-hook (&optional match-type nick message) + "Shows a notification, when user's nick was mentioned. +If the buffer is currently not visible, makes it sticky." + (setq last-message message) + (if (or (null match-type) (not (eq match-type 'fool))) + (let (alert-log-messages) + (alert (or message (buffer-string)) + :severity (if (string-match "grfn" (or message "")) + 'high 'low) + :title (or nick (buffer-name)) + :data `(:message ,(or message (buffer-string)) + :channel ,(or nick (buffer-name))))))) + +(add-hook 'erc-text-matched-hook 'my-erc-hook) +(add-hook 'erc-insert-modify-hook 'my-erc-hook) + +(defun my-erc-define-alerts (&rest ignore) + ;; Unless the user has recently typed in the ERC buffer, highlight the fringe + (alert-add-rule + :status '(buried visible idle) + :severity '(moderate high urgent) + :mode 'erc-mode + :predicate + #'(lambda (info) + (and (not (eq (current-buffer) (plist-get info :buffer))) + (string-match "grfn:" (plist-get info :message)))) + :persistent + #'(lambda (info) + ;; If the buffer is buried, or the user has been idle for + ;; `alert-reveal-idle-time' seconds, make this alert + ;; persistent. Normally, alerts become persistent after + ;; `alert-persist-idle-time' seconds. + (memq (plist-get info :status) '(buried idle))) + :style 'message + :continue t) + + (alert-add-rule + :status 'buried + :mode 'erc-mode + :predicate #'erc-alert-important-p + :style 'libnotify + :append t) + + (alert-add-rule + :status 'buried + :mode 'erc-mode + :predicate #'erc-alert-important-p + :style 'message + :append t) + + (alert-add-rule + :mode 'erc-mode + :predicate #'erc-alert-important-p + :style 'log + :append t) + + (alert-add-rule :mode 'erc-mode :style 'ignore :append t)) + +(add-hook 'erc-connect-pre-hook 'my-erc-define-alerts) + +(defun fix-irc-message (msg) + (let ((msg (s-trim msg))) + (if (string-equal msg ":q") "" msg))) + +(advice-add #'erc-user-input :filter-return #'fix-irc-message) + +(comment + (my-erc-define-alerts) + ) diff --git a/users/grfn/emacs.d/lisp.el b/users/grfn/emacs.d/lisp.el new file mode 100644 index 000000000000..c45cc7e6e381 --- /dev/null +++ b/users/grfn/emacs.d/lisp.el @@ -0,0 +1,38 @@ +;;; -*- lexical-binding: t; -*- + +(defun grfn/sly-panettone () + (interactive) + (sly + (concat + (s-trim + (shell-command-to-string + "nix-build -o sbcl -E 'with import ~/code/depot {}; nix.buildLisp.sbclWith [web.panettone]'")) + "/bin/sbcl"))) + +(defun grfn/setup-lisp () + (interactive) + (unless paxedit-mode (paxedit-mode 1)) + (rainbow-delimiters-mode) + (flycheck-mode -1)) + +(add-hook 'common-lisp-lisp-mode-hook #'grfn/setup-lisp) + +(defun sly-run-tests () + (interactive) + ;; TODO: handle other test frameworks + (let ((orig-window (get-buffer-window))) + (sly-eval '(fiveam:run!)) + (funcall-interactively #'sly-mrepl-sync) + (select-window orig-window))) + +(map! + (:map sly-mode-map + :n "g \\" #'sly-mrepl-sync + :n "g d" #'sly-edit-definition + :n "K" #'sly-documentation + :n "g SPC" #'sly-compile-and-load-file + :n "g RET" #'sly-run-tests) + + (:map sly-mrepl-mode-map + "C-k" #'sly-mrepl-previous-prompt + "C-r" #'isearch-backward)) diff --git a/users/grfn/emacs.d/nix-clangd.sh b/users/grfn/emacs.d/nix-clangd.sh new file mode 100755 index 000000000000..16f6252d8b27 --- /dev/null +++ b/users/grfn/emacs.d/nix-clangd.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +CLANGD_FLAGS=--compile-commands-dir=/home/grfn/builds/tvix \ + nix-shell /home/grfn/code/depot \ + -A third_party.nix \ + --run nix-clangd diff --git a/users/grfn/emacs.d/nix.el b/users/grfn/emacs.d/nix.el new file mode 100644 index 000000000000..ec5b474af218 --- /dev/null +++ b/users/grfn/emacs.d/nix.el @@ -0,0 +1,30 @@ +;;; -*- lexical-binding: t; -*- + +(defun nix-buffer-type () + "Returns: + +'home-manager, if the current buffer is a home-manager module +'nixos, if the current buffer is a nixos module +nil, if none of the above are the case" + (when buffer-file-name + (pcase buffer-file-name + ((rx (0+ nonl) "system/home" (0+ nonl) ".nix" eos) + 'home-manager) + ((rx (0+ nonl) "system/system" (0+ nonl) ".nix" eos) + 'nixos)))) + +(defun set-nix-compile-command () + "Set the compile command for the current buffer based on the type of nix +buffer it is, per `nix-buffer-type'" + (interactive) + (when-let ((btype (nix-buffer-type))) + (setq-local + compile-command + (case btype + ('home-manager "home-manager switch") + ('nixos "sudo nixos-rebuild switch"))))) + +(add-hook 'nix-mode-hook #'set-nix-compile-command) + +(map! (:map nix-mode-map + (:n "g SPC" #'compile))) diff --git a/users/grfn/emacs.d/org-alerts.el b/users/grfn/emacs.d/org-alerts.el new file mode 100644 index 000000000000..8e6c3e0417ff --- /dev/null +++ b/users/grfn/emacs.d/org-alerts.el @@ -0,0 +1,188 @@ +;;; -*- lexical-binding: t; -*- + +;;; Commentary: + +;;; Code: + +(require 's) +(require 'dash) +(require 'alert) +(require 'org-agenda) + + +(defvar grfn/org-alert-interval 300 + "Interval in seconds to recheck and display deadlines.") + + +(defvar grfn/org-alert-notification-title "*org*" + "Title to be sent with notify-send.") + +(defvar grfn/org-alert-headline-regexp "\\(Sched.+:.+\\|Deadline:.+\\)" + "Regexp for headlines to search in agenda buffer.") + +(defun grfn/org-alert--strip-prefix (headline) + "Remove the scheduled/deadline prefix from HEADLINE." + (replace-regexp-in-string ".*:\s+" "" headline)) + + +(defun grfn/org-alert--unique-headlines (regexp agenda) + "Return unique headlines from the results of REGEXP in AGENDA." + (let ((matches (-distinct (-flatten (s-match-strings-all regexp agenda))))) + (--map (grfn/org-alert--strip-prefix it) matches))) + + +(defun grfn/org-alert--get-headlines () + "Return the current org agenda as text only." + (with-temp-buffer + (let ((org-agenda-sticky nil) + (org-agenda-buffer-tmp-name (buffer-name))) + (ignore-errors (org-agenda-list nil "TODAY" 1)) + (grfn/org-alert--unique-headlines + grfn/org-alert-headline-regexp + (buffer-substring-no-properties (point-min) (point-max)))))) + +(defun grfn/parse-range-string (str) + (when + (string-match (rx (group (repeat 2 (any digit)) + ":" + (repeat 2 (any digit))) + (optional + (and + "-" + (group (repeat 2 (any digit)) + ":" + (repeat 2 (any digit)))))) + str) + (list + (org-read-date nil t + (match-string 1 str)) + (when-let ((et (match-string 2 str))) (org-read-date nil t et))))) + +(defun grfn/start-time-from-range-string (str) + (pcase-let ((`(,start-time . _) (grfn/parse-range-string str))) + start-time)) + +(comment + (org-agenda-list nil "TODAY" 1) + + (grfn/org-alert--get-headlines) + (setq --src + (with-temp-buffer + (let ((org-agenda-sticky nil) + (org-agenda-buffer-tmp-name (buffer-name))) + (ignore-errors (org-agenda-list nil "TODAY" 1)) + (buffer-substring-no-properties (point-min) (point-max))))) + + (setq --entries + (with-temp-buffer + (let ((inhibit-redisplay t) + (org-agenda-sticky nil) + (org-agenda-buffer-tmp-name (buffer-name)) + (org-agenda-buffer-name (buffer-name)) + (org-agenda-buffer (current-buffer))) + (org-agenda-get-day-entries + (cadr (org-agenda-files nil 'ifmode)) + (calendar-gregorian-from-absolute + (time-to-days (org-read-date nil t "TODAY"))))))) + + (loop for k in (text-properties-at 0 (car --entries)) + by #'cddr + collect k) + + (--map (substring-no-properties (get-text-property 0 'txt it)) --entries) + (--map (get-text-property 0 'time it) --entries) + (current-time) + + (format-time-string "%R" (org-read-date nil t "10:00-11:00")) + + (grfn/start-time-from-range-string "10:00") + + (current-time-string (org-read-date nil t "10:00-11:00")) + + (todo-state + org-habit-p + priority + warntime + ts-date + date + type + org-hd-marker + org-marker + face + undone-face + help-echo + mouse-face + done-face + org-complex-heading-regexp + org-todo-regexp + org-not-done-regexp + dotime + format + extra + time + level + txt + breadcrumbs + duration + time-of-day + org-lowest-priority + org-highest-priority + tags + org-category) + + (propertize) + + --src + ) + + +(defun grfn/org-alert--headline-complete? (headline) + "Return whether HEADLINE has been completed." + (--any? (s-starts-with? it headline) org-done-keywords-for-agenda)) + + +(defun grfn/org-alert--filter-active (deadlines) + "Remove any completed headings from the provided DEADLINES." + (-remove 'grfn/org-alert--headline-complete? deadlines)) + + +(defun grfn/org-alert--strip-states (deadlines) + "Remove the todo states from DEADLINES." + (--map (s-trim (s-chop-prefixes org-todo-keywords-for-agenda it)) deadlines)) + + +(defun grfn/org-alert-check () + "Check for active, due deadlines and initiate notifications." + (interactive) + ;; avoid interrupting current command. + (unless (minibufferp) + (save-window-excursion + (save-excursion + (save-restriction + (let ((active (grfn/org-alert--filter-active (grfn/org-alert--get-headlines)))) + (dolist (dl (grfn/org-alert--strip-states active)) + (alert dl :title grfn/org-alert-notification-title)))))) + (when (get-buffer org-agenda-buffer-name) + (ignore-errors + (with-current-buffer org-agenda-buffer-name + (org-agenda-redo t)))))) + + +(defun grfn/org-alert-enable () + "Enable the notification timer. Cancels existing timer if running." + (interactive) + (grfn/org-alert-disable) + (run-at-time 0 grfn/org-alert-interval 'grfn/org-alert-check)) + + +(defun grfn/org-alert-disable () + "Cancel the running notification timer." + (interactive) + (dolist (timer timer-list) + (if (eq (elt timer 5) 'grfn/org-alert-check) + (cancel-timer timer)))) + + + +(provide 'grfn/org-alert) +;;; grfn/org-alert.el ends here diff --git a/users/grfn/emacs.d/org-config.el b/users/grfn/emacs.d/org-config.el new file mode 100644 index 000000000000..81fb35a1cbda --- /dev/null +++ b/users/grfn/emacs.d/org-config.el @@ -0,0 +1,193 @@ +;;; -*- lexical-binding: t; -*- + +(defun notes-file (f) + (concat org-directory (if (string-prefix-p "/" f) "" "/") f)) + +(defun grfn/org-project-tag->key (tag) + (s-replace-regexp "^project__" "" tag)) + +(defun grfn/org-project-tag->name (tag) + (s-titleized-words + (s-join " " (s-split "_" (grfn/org-project-tag->key tag))))) + +(defun grfn/org-project-tag->keys (tag) + (s-join "" (cons "p" + (-map (lambda (s) (substring-no-properties s 0 1)) + (s-split "_" (grfn/org-project-tag->key tag)))))) + +(defun grfn/org-projects->agenda-commands (project-tags) + (loop for tag in project-tags + collect `(,(grfn/org-project-tag->keys tag) + ,(grfn/org-project-tag->name tag) + tags-todo + ,tag))) + +(defun grfn/org-projects () + (loop for (tag) in + (org-global-tags-completion-table + (directory-files-recursively "~/notes" "\\.org$")) + when (s-starts-with-p "project__" tag) + collect tag)) + +(comment + (grfn/org-projects->agenda-commands (grfn/org-projects)) + ) + +(setq + org-directory (expand-file-name "~/notes") + +org-dir (expand-file-name "~/notes") + org-default-notes-file (concat org-directory "/inbox.org") + +org-default-todo-file (concat org-directory "/inbox.org") + org-agenda-files (directory-files-recursively + "~/notes" "\\.org$") + org-refile-targets '((org-agenda-files :maxlevel . 3)) + org-outline-path-complete-in-steps nil + org-refile-use-outline-path t + org-file-apps `((auto-mode . emacs) + (,(rx (or (and "." (optional "x") (optional "htm") (optional "l") buffer-end) + (and buffer-start "http" (optional "s") "://"))) + . "firefox %s") + (,(rx ".pdf" buffer-end) . "apvlv %s") + (,(rx "." (or "png" + "jpg" + "jpeg" + "gif" + "tif" + "tiff") + buffer-end) + . "feh %s")) + org-log-done 'time + org-archive-location "~/notes/trash::* From %s" + org-cycle-separator-lines 2 + org-hidden-keywords '(title) + org-tags-column -130 + org-ellipsis "โฆ" + org-imenu-depth 9 + org-capture-templates + `(("t" "Todo" entry + (file +org-default-todo-file) + "* TODO %?\n%i" + :kill-buffer t) + + ("m" "Email" entry + (file +org-default-todo-file) + "* TODO [[%L][%:subject]] :email:\n%i") + + ("n" "Notes" entry + (file +org-default-todo-file) + "* %U %?\n%i" + :prepend t + :kill-buffer t) + + ("c" "Task note" entry + (clock) + "* %U %?\n%i[%l[Context]]\n" + :kill-buffer t + :unnarrowed t) + + ("p" "Projects") + ("px" "Xanthous" entry + (file+headline ,(notes-file "xanthous.org") "Backlog") + "* TODO %?\nContext %a\nIn task: %K") + ("pt" "Tvix" entry + (file+headline ,(notes-file "tvix.org") "Tvix TODO") + "* TODO %?\nContext %a\nIn task: %K") + ("pw" "Windtunnel" entry + (file+headline ,(notes-file "windtunnel.org") "Tasks") + "* TODO %i%?\nContext: %a\nIn task: %K") + + ("d" "Data recording") + ) + + org-capture-templates-contexts + `(("px" ((in-file . "/home/grfn/code/depot/users/grfn/xanthous/.*")))) + + org-deadline-warning-days 1 + org-agenda-skip-scheduled-if-deadline-is-shown 'todo + org-todo-keywords '((sequence "TODO(t)" "ACTIVE(a)" "|" "DONE(d)" "RUNNING(r)") + (sequence "NEXT(n)" "WAITING(w)" "LATER(l)" "|" "CANCELLED(c)")) + org-agenda-custom-commands + `(("S" "Sprint Tasks" tags-todo "sprint") + ("i" "Inbox" tags "inbox") + ("r" "Running jobs" todo "RUNNING") + ("w" "@Work" tags-todo "@work") + ("n" . "Next...") + ("np" "Next Sprint" tags-todo "next_sprint|sprint_planning") + + ("p" . "Project...") + ,@(grfn/org-projects->agenda-commands (grfn/org-projects))) + + org-agenda-dim-blocked-tasks nil + org-enforce-todo-dependencies nil + + org-babel-clojure-backend 'cider) + + +(defun +grfn/insert-work-template () + (interactive) + (goto-char (point-min)) + (forward-line) + (insert "#+TODO: TODO(t) NEXT(n) ACTIVE(a) | DONE(d) PR(p) RUNNING(r) TESTING(D) +#+TODO: BLOCKED(b) BACKLOG(l) PROPOSED(o) | CANCELLED(c) +#+FILETAGS: @work +#+FILETAGS: @work +#+PROPERTY: Effort_ALL 0 4:00 8:00 12:00 20:00 32:00 +#+PROPERTY: ESTIMATE_ALL 0 1 2 3 5 8 +#+PROPERTY: STORY-TYPE_ALL Feature Bug Chore +#+PROPERTY: NOBLOCKING t +#+COLUMNS: %TODO %40ITEM(Task) %17EFFORT(Estimated){:} %CLOCKSUM(Time Spent) %17STORY-TYPE(Type) %TAGS")) + +(defun +grfn/insert-org-template () + (interactive) + (pcase (buffer-file-name) + ((s-contains "/work/") (+grfn/insert-work-template)))) + +;;; TODO: this doesn't work? +(define-auto-insert "\\.org?$" #'grfn/insert-org-template t) + +(defun forge--post-submit-around---link-pr-to-org-item + (orig) + (let ((cb (funcall orig))) + (lambda (value headers status req) + (prog1 (funcall cb value headers status req) + (grfn/at-org-clocked-in-item + (let ((url (alist-get 'html_url value)) + (number (alist-get 'number value))) + (org-set-property + "pull-request" + (org-make-link-string + url + (format "%s/%s/%d" + (->> value + (alist-get 'base) + (alist-get 'repo) + (alist-get 'name)) + (->> value + (alist-get 'base) + (alist-get 'repo) + (alist-get 'owner) + (alist-get 'login)) + number))))))))) + +(advice-add + #'forge--post-submit-callback + :around #'forge--post-submit-around---link-pr-to-org-item) + +(defun +grfn/org-setup () + (setq-local truncate-lines -1) + (display-line-numbers-mode -1) + (line-number-mode -1)) + +(add-hook 'org-mode-hook #'+grfn/org-setup) + +(set-face-foreground 'org-block +solarized-s-base00) +(setq whitespace-global-modes '(not org-mode magit-mode vterm-mode)) +(setf (alist-get 'file org-link-frame-setup) 'find-file-other-window) +(set-face-foreground 'org-block +solarized-s-base00) + +;; (add-hook! org-mode +;; (set-company-backend! 'org-mode +;; '(:separate company-ob-postgresql +;; company-dabbrev +;; company-yasnippet +;; company-ispell))) diff --git a/users/grfn/emacs.d/org-gcal.el b/users/grfn/emacs.d/org-gcal.el new file mode 100644 index 000000000000..3e315c5e6046 --- /dev/null +++ b/users/grfn/emacs.d/org-gcal.el @@ -0,0 +1,181 @@ +;;; -*- lexical-binding: t; -*- + +(require 'aio) +(require 'parse-time) + +(setq-local lexical-binding t) +(setq plstore-cache-passphrase-for-symmetric-encryption t) + +(defvar gcal-client-id) +(defvar gcal-client-secret) + +(defvar google-calendar-readonly-scope + "https://www.googleapis.com/auth/calendar.readonly") + +(defvar events-file "/home/grfn/notes/events.org") + +(defun google--get-token (scope client-id client-secret) + (oauth2-auth-and-store + "https://accounts.google.com/o/oauth2/v2/auth" + "https://oauth2.googleapis.com/token" + scope + client-id + client-secret)) + +(cl-defun google--request (url &key method params scope) + (let ((p (aio-promise)) + (auth-token (google--get-token scope gcal-client-id gcal-client-secret))) + (oauth2-refresh-access auth-token) + (oauth2-url-retrieve + auth-token + url + (lambda (&rest _) + (goto-char (point-min)) + (re-search-forward "^$") + (let ((resp (json-parse-buffer :object-type 'alist))) + (aio-resolve p (lambda () resp)))) + nil + (or method "GET") + params) + p)) + +(cl-defun list-events (&key min-time max-time) + (google--request + (concat + "https://www.googleapis.com/calendar/v3/calendars/griffin@urbint.com/events" + "?timeMin=" (format-time-string "%Y-%m-%dT%T%z" min-time) + "&timeMax=" (format-time-string "%Y-%m-%dT%T%z" max-time)) + :scope google-calendar-readonly-scope)) + + +(defun last-week-events () + (list-events :min-time (time-subtract + (current-time) + (seconds-to-time + (* 60 60 24 7))) + :max-time (current-time))) + +(defun next-week-events () + (list-events :min-time (current-time) + :max-time (time-add + (current-time) + (seconds-to-time + (* 60 60 24 7))))) + +(defun attending-event? (event) + (let* ((attendees (append (alist-get 'attendees event) nil)) + (self (--find (alist-get 'self it) attendees))) + (equal "accepted" (alist-get 'responseStatus self)))) + +(defun event->org-headline (event level) + (cl-flet ((make-time + (key) + (when-let ((raw-time (->> event (alist-get key) (alist-get 'dateTime)))) + (format-time-string + (org-time-stamp-format t) + (parse-iso8601-time-string raw-time))))) + (if-let ((start-time (make-time 'start)) + (end-time (make-time 'end))) + (s-format + "${headline} [[${htmlLink}][${summary}]] :event: +${startTime}--${endTime} +:PROPERTIES: +${location-prop} +:EVENT: ${htmlLink} +:END: + +${description}" + (function + (lambda (k m) + (or (alist-get (intern k) m) + (format "key not found: %s" k)))) + (append + event + `((headline . ,(make-string level ?*)) + (startTime . ,start-time) + (endTime . ,end-time) + (location-prop + . ,(if-let ((location (alist-get 'location event))) + (s-lex-format ":LOCATION: ${location}") + ""))))) + ""))) + +(comment + (alist-get 'foo nil) + ) + +(defun write-events (events) + (with-current-buffer (find-file-noselect events-file) + (save-mark-and-excursion + (save-restriction + (widen) + (erase-buffer) + (goto-char (point-min)) + (insert "#+TITLE: Events") + (newline) (newline) + (prog1 + (loop for event in (append events nil) + when (attending-event? event) + do + (insert (event->org-headline event 1)) + (newline) + sum 1) + (org-align-tags t)))))) + +(defun +grfn/sync-events () + (interactive) + (let* ((events (alist-get 'items (aio-wait-for (next-week-events)))) + (num-written (write-events events))) + (message "Successfully wrote %d events" num-written))) + +(comment + ((kind . "calendar#event") + (etag . "\"3174776941020000\"") + (id . "SNIP") + (status . "confirmed") + (htmlLink . "https://www.google.com/calendar/event?eid=SNIP") + (created . "2020-04-01T13:30:09.000Z") + (updated . "2020-04-20T13:14:30.510Z") + (summary . "SNIP") + (description . "SNIP") + (location . "SNIP") + (creator + (email . "griffin@urbint.com") + (self . t)) + (organizer + (email . "griffin@urbint.com") + (self . t)) + (start + (dateTime . "2020-04-01T12:00:00-04:00") + (timeZone . "America/New_York")) + (end + (dateTime . "2020-04-01T12:30:00-04:00") + (timeZone . "America/New_York")) + (recurrence . + ["RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE"]) + (iCalUID . "SNIP") + (sequence . 0) + (attendees . + [((email . "griffin@urbint.com") + (organizer . t) + (self . t) + (responseStatus . "accepted")) + ((email . "SNIP") + (displayName . "SNIP") + (responseStatus . "needsAction"))]) + (extendedProperties + (private + (origRecurringId . "309q48kc1dihsvbi13pnlimb5a")) + (shared + (origRecurringId . "309q48kc1dihsvbi13pnlimb5a"))) + (reminders + (useDefault . t))) + + (require 'icalendar) + + (icalendar--convert-recurring-to-diary + nil + "RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE" + ) + + ) diff --git a/users/grfn/emacs.d/org-query.el b/users/grfn/emacs.d/org-query.el new file mode 100644 index 000000000000..0b987280f2a1 --- /dev/null +++ b/users/grfn/emacs.d/org-query.el @@ -0,0 +1,100 @@ +;;; -*- lexical-binding: t; -*- + +(require 'org) +(require 'org-agenda) +(require 'inflections) + +(defun grfn/org-agenda-entry->element (agenda-entry) + ;; ??? + ()) + +(defun org-elements-agenda-match (match &optional todo-only) + (setq match + (propertize match 'inherited t)) + (with-temp-buffer + (let ((inhibit-redisplay (not debug-on-error)) + (org-agenda-sticky nil) + (org-agenda-buffer-tmp-name (buffer-name)) + (org-agenda-buffer-name (buffer-name)) + (org-agenda-buffer (current-buffer)) + (matcher (org-make-tags-matcher match)) + result) + (org-agenda-prepare (concat "TAGS " match)) + (setq match (car matcher) + matcher (cdr matcher)) + (dolist (file (org-agenda-files nil 'ifmode) + result) + (catch 'nextfile + (org-check-agenda-file file) + (when-let ((buffer (if (file-exists-p file) + (org-get-agenda-file-buffer file) + (error "No such file %s" file)))) + (with-current-buffer buffer + (unless (derived-mode-p 'org-mode) + (error "Agenda file %s is not in Org mode" file)) + (save-excursion + (save-restriction + (if (eq buffer org-agenda-restrict) + (narrow-to-region org-agenda-restrict-begin + org-agenda-restrict-end) + (widen)) + (setq result + (append result (org-scan-tags + 'agenda + matcher + todo-only)))))))))))) + +(defun grfn/num-inbox-items () + (length (org-elements-agenda-match "inbox" t))) + +(defun grfn/num-inbox-items-message () + (let ((n (grfn/num-inbox-items))) + (if (zerop n) "" + (format "%d %s" + n + (if (= 1 n) "item" "items"))))) + +(defmacro grfn/at-org-clocked-in-item (&rest body) + `(when (org-clocking-p) + (let ((m org-clock-marker)) + (with-current-buffer (marker-buffer m) + (save-mark-and-excursion + (goto-char m) + (org-back-to-heading t) + ,@body))))) + +(defun grfn/org-element-clocked-in-task () + (grfn/at-org-clocked-in-item + (org-element-at-point))) + +(comment + (grfn/org-element-clocked-in-task) + (org-element-property :title (grfn/org-element-clocked-in-task)) + ) + +(defun grfn/minutes->hours:minutes (minutes) + (format "%d:%02d" + (floor (/ minutes 60)) + (mod minutes 60))) + +(comment + (grfn/minutes->hours:minutes 1) ; => "0:01" + (grfn/minutes->hours:minutes 15) ; => "0:15" + (grfn/minutes->hours:minutes 130) ; => "2:10" + ) + +(defun grfn/org-current-clocked-in-task-message () + (if (org-clocking-p) + (format "(%s) [%s]" + (->> (grfn/org-element-clocked-in-task) + (org-element-property :title) + (car) + (substring-no-properties) + (s-trim)) + (grfn/minutes->hours:minutes + (org-clock-get-clocked-time))) + "")) + +(comment + (grfn/org-current-clocked-in-task-message) + ) diff --git a/users/grfn/emacs.d/packages.el b/users/grfn/emacs.d/packages.el new file mode 100644 index 000000000000..5a580cad17e0 --- /dev/null +++ b/users/grfn/emacs.d/packages.el @@ -0,0 +1,152 @@ +;; -*- no-byte-compile: t; -*- +;;; private/grfn/packages.el + +(package! moody) + +;; Editor +(package! solarized-theme) +(package! fill-column-indicator) +(package! flx) +(package! general + :recipe (:host github :repo "noctuid/general.el")) +(package! fill-column-indicator) +(package! writeroom-mode) +(package! dash) +(package! w3m) +(package! rainbow-mode) +(package! string-inflection) + +;;; Org +(package! org-tracker + :recipe (:host file + :local-repo "~/code/org-tracker")) +(package! org-alert) +(package! ob-http) +(package! ob-ipython) +(package! ob-async) +(package! org-recent-headings) +(package! org-sticky-header) +(package! gnuplot) +(package! gnuplot-mode) + +;; Presentation +(package! epresent) +(package! org-tree-slide) +(package! ox-reveal) + +;; Slack etc +(package! slack) +(package! alert) + +;; Git +(package! evil-magit) +(package! marshal) +(package! forge) +(package! + github-review + :recipe + (:host github + :repo "charignon/github-review" + :files ("github-review.el"))) + +;; Elisp +(package! dash) +(package! dash-functional) +(package! s) +(package! request) +(package! predd + :recipe (:host github :repo "skeeto/predd")) +(package! aio) + +;; Haskell +(package! lsp-haskell) +(package! counsel-etags) + +;;; LSP +(package! lsp-mode) +(package! lsp-ui :recipe (:host github :repo "emacs-lsp/lsp-ui")) +(package! company-lsp) +(package! lsp-treemacs) + +;; Rust +(package! rustic :disable t) +;; (package! racer :disable t) +(package! cargo) + +;; Lisp +(package! paxedit) + +;; Javascript +(package! flow-minor-mode) +(package! flycheck-flow) +(package! company-flow) +(package! prettier-js) + +;; GraphQL +(package! graphql-mode) + +;; Haskell +(package! lsp-mode) +(package! lsp-ui) +(package! lsp-haskell) +(package! company-lsp) +;; (package! lsp-imenu) + +;; Clojure +(package! flycheck-clojure) + +;; SQL +(package! sqlup-mode) +(package! emacsql) +(package! emacsql-psql) + +;;; Python +(package! pyimport) +;; (package! yapfify) +(package! blacken) + + +;;; Desktop interaction +(package! counsel-spotify) + +;;; Dhall +(package! dhall-mode) + +;;; Kubernetes +(package! kubernetes) +(package! kubernetes-evil) +(package! k8s-mode) + +;;; Stack Exchange +(package! sx) + +;;; Nix +(package! nix-update + :recipe (:host github + :repo "glittershark/nix-update-el")) +(package! direnv) + +;;; Sequence diagrams +(package! wsd-mode + :recipe (:host github + :repo "josteink/wsd-mode")) + +;;; logic? +(package! metal-mercury-mode + :recipe (:host github + :repo "ahungry/metal-mercury-mode")) +(package! flycheck-mercury) + +(package! terraform-mode) +(package! company-terraform) + +(package! jsonnet-mode) + +;;; +(package! znc + :recipe (:host github + :repo "sshirokov/ZNC.el")) + +;;; cpp +(package! protobuf-mode) +(package! clang-format+) diff --git a/users/grfn/emacs.d/rust.el b/users/grfn/emacs.d/rust.el new file mode 100644 index 000000000000..689d843e78e1 --- /dev/null +++ b/users/grfn/emacs.d/rust.el @@ -0,0 +1,39 @@ +;;; -*- lexical-binding: t; -*- + +(add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode)) + +(defun grfn/rust-setup () + (interactive) + + (push '(?> . ("<" . ">")) evil-surround-pairs-alist) + (push '(?< . ("< " . " >")) evil-surround-pairs-alist) + + (setq lsp-rust-server 'rust-analyzer) + (setq-local whitespace-line-column 100 + fill-column 100) + (setq rust-format-show-buffer nil) + (setq lsp-rust-analyzer-import-merge-behaviour "last" + lsp-rust-analyzer-cargo-watch-command "clippy" + lsp-rust-analyzer-cargo-watch-args ["--target-dir" "/home/grfn/code/readyset/readyset/target/rust-analyzer"] + lsp-ui-doc-enable t) + (rust-enable-format-on-save) + (lsp)) + +(add-hook 'rust-mode-hook #'grfn/rust-setup) + +(map! + (:map rust-mode-map + :n "g RET" #'lsp-rust-analyzer-run + :n "g R" #'lsp-find-references + :n "g d" #'lsp-find-definition + (:localleader + "m" #'lsp-rust-analyzer-expand-macro))) + +(comment + (flycheck-get-next-checkers 'lsp) + (flycheck-add-next-checker) + (flycheck-get-next-checkers 'lsp) + ) + +(set-company-backend! 'rust-mode + '(:separate company-capf company-yasnippet)) diff --git a/users/grfn/emacs.d/show-matching-paren.el b/users/grfn/emacs.d/show-matching-paren.el new file mode 100644 index 000000000000..ab65a912a8d1 --- /dev/null +++ b/users/grfn/emacs.d/show-matching-paren.el @@ -0,0 +1,61 @@ +;;; -*- lexical-binding: t; -*- + +;;; https://with-emacs.com/posts/ui-hacks/show-matching-lines-when-parentheses-go-off-screen/ + +;; we will call `blink-matching-open` ourselves... +(remove-hook 'post-self-insert-hook + #'blink-paren-post-self-insert-function) +;; this still needs to be set for `blink-matching-open` to work +(setq blink-matching-paren 'show) + +(let ((ov nil)) ; keep track of the overlay + (advice-add + #'show-paren-function + :after + (defun show-paren--off-screen+ (&rest _args) + "Display matching line for off-screen paren." + (when (overlayp ov) + (delete-overlay ov)) + ;; check if it's appropriate to show match info, + ;; see `blink-paren-post-self-insert-function' + (when (and (overlay-buffer show-paren--overlay) + (not (or cursor-in-echo-area + executing-kbd-macro + noninteractive + (minibufferp) + this-command)) + (and (not (bobp)) + (memq (char-syntax (char-before)) '(?\) ?\$))) + (= 1 (logand 1 (- (point) + (save-excursion + (forward-char -1) + (skip-syntax-backward "/\\") + (point)))))) + ;; rebind `minibuffer-message' called by + ;; `blink-matching-open' to handle the overlay display + (cl-letf (((symbol-function #'minibuffer-message) + (lambda (msg &rest args) + (let ((msg (apply #'format-message msg args))) + (setq ov (display-line-overlay+ + (window-start) msg )))))) + (blink-matching-open)))))) + +(defun display-line-overlay+ (pos str &optional face) + "Display line at POS as STR with FACE. + +FACE defaults to inheriting from default and highlight." + (let ((ol (save-excursion + (goto-char pos) + (make-overlay (line-beginning-position) + (line-end-position))))) + (overlay-put ol 'display str) + (overlay-put ol 'face + (or face '(:inherit default :inherit highlight))) + ol)) + +(setq show-paren-style 'paren + show-paren-delay 0.03 + show-paren-highlight-openparen t + show-paren-when-point-inside-paren nil + show-paren-when-point-in-periphery t) +(show-paren-mode 1) diff --git a/users/grfn/emacs.d/slack-snippets.el b/users/grfn/emacs.d/slack-snippets.el new file mode 100644 index 000000000000..b5bd4db7482c --- /dev/null +++ b/users/grfn/emacs.d/slack-snippets.el @@ -0,0 +1,227 @@ +;;; -*- lexical-binding: t; -*- + +(require 'dash) +(require 'dash-functional) +(require 'request) + +;;; +;;; Configuration +;;; + +(defvar slack/token nil + "Legacy (https://api.slack.com/custom-integrations/legacy-tokens) access token") + +(defvar slack/include-public-channels 't + "Whether or not to inclue public channels in the list of conversations") + +(defvar slack/include-private-channels 't + "Whether or not to inclue public channels in the list of conversations") + +(defvar slack/include-im 't + "Whether or not to inclue IMs (private messages) in the list of conversations") + +(defvar slack/include-mpim nil + "Whether or not to inclue multi-person IMs (multi-person private messages) in + the list of conversations") + +;;; +;;; Utilities +;;; + +(defmacro comment (&rest _body) + "Comment out one or more s-expressions" + nil) + +(defun ->list (vec) (append vec nil)) + +(defun json-truthy? (x) (and x (not (equal :json-false x)))) + +;;; +;;; Generic API integration +;;; + +(defvar slack/base-url "https://slack.com/api") + +(defun slack/get (path params &optional callback) + "params is an alist of query parameters" + (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback))) + (params (car params-callback)) (callback (cdr params-callback)) + (params (append `(("token" . ,slack/token)) params)) + (url (concat (file-name-as-directory slack/base-url) path))) + (request url + :type "GET" + :params params + :parser 'json-read + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data)))))) + +(defun slack/post (path params &optional callback) + (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback))) + (params (car params-callback)) (callback (cdr params-callback)) + (url (concat (file-name-as-directory slack/base-url) path))) + (request url + :type "POST" + :data (json-encode params) + :headers `(("Content-Type" . "application/json") + ("Authorization" . ,(format "Bearer %s" slack/token))) + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data)))))) + + +;;; +;;; Specific API endpoints +;;; + +;; Users + +(defun slack/users (cb) + "Returns users as (id . name) pairs" + (slack/get + "users.list" + (lambda (data) + (->> data + (assoc-default 'members) + ->list + (-map (lambda (user) + (cons (assoc-default 'id user) + (assoc-default 'real_name user)))) + (-filter #'cdr) + (funcall cb))))) + +(comment + (slack/get + "users.list" + (lambda (data) (setq response-data data))) + + (slack/users (lambda (data) (setq --users data))) + + ) + +;; Conversations + +(defun slack/conversation-types () + (->> + (list (when slack/include-public-channels "public_channel") + (when slack/include-private-channels "private_channel") + (when slack/include-im "im") + (when slack/include-mpim "mpim")) + (-filter #'identity) + (s-join ","))) + +(defun channel-label (chan users-alist) + (cond + ((json-truthy? (assoc-default 'is_channel chan)) + (format "#%s" (assoc-default 'name chan))) + ((json-truthy? (assoc-default 'is_im chan)) + (let ((user-id (assoc-default 'user chan))) + (format "Private message with %s" (assoc-default user-id users-alist)))) + ((json-truthy? (assoc-default 'is_mpim chan)) + (->> chan + (assoc-default 'purpose) + (assoc-default 'value))))) + +(defun slack/conversations (cb) + "Calls `cb' with (id . '((label . \"label\") '(topic . \"topic\") '(purpose . \"purpose\"))) pairs" + (slack/get + "conversations.list" + `(("types" . ,(slack/conversation-types)) + ("exclude-archived" . "true")) + (lambda (data) + (setq --data data) + (slack/users + (lambda (users) + (->> data + (assoc-default 'channels) + ->list + (-map + (lambda (chan) + (cons (assoc-default 'id chan) + `((label . ,(channel-label chan users)) + (topic . ,(->> chan + (assoc-default 'topic) + (assoc-default 'value))) + (purpose . ,(->> chan + (assoc-default 'purpose) + (assoc-default 'value))))))) + (funcall cb))))))) + +(comment + (slack/get + "conversations.list" + '(("types" . "public_channel,private_channel,im,mpim")) + (lambda (data) (setq response-data data))) + + (slack/get + "conversations.list" + '(("types" . "im")) + (lambda (data) (setq response-data data))) + + (slack/conversations + (lambda (convos) (setq --conversations convos))) + + ) + +;; Messages + +(cl-defun slack/post-message + (&key text channel-id (on-success #'identity)) + (slack/post "chat.postMessage" + `((text . ,text) + (channel . ,channel-id) + (as_user . t)) + on-success)) + +(comment + + (slack/post-message + :text "hi slackbot" + :channel-id slackbot-channel-id + :on-success (lambda (data) (setq resp data))) + + ) + +;;; +;;; Posting code snippets to slack +;;; + +(defun prompt-for-channel (cb) + (slack/conversations + (lambda (conversations) + (ivy-read + "Select channel: " + ;; TODO want to potentially use purpose / topic stuff here + (->> conversations + (-filter (lambda (c) (assoc-default 'label (cdr c)))) + (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan))) + (id (car chan))) + (propertize label 'channel-id id))))) + :history 'slack/channel-history + :action (lambda (selected) + (let ((channel-id (get-text-property 0 'channel-id selected))) + (funcall cb channel-id) + (message "Sent message to %s" selected)))))) + nil) + +(comment + (prompt-for-channel #'message) + (->> --convos + (-filter (lambda (c) (assoc-default 'label (cdr c)))) + (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan))) + (id (car chan))) + (propertize label 'channel-id id))))) + + (->> --convos (car) (cdr) (assoc-default 'label)) + ) + +(defun slack-send-code-snippet (&optional snippet-text) + (interactive + (list (buffer-substring-no-properties (mark) (point)))) + (prompt-for-channel + (lambda (channel-id) + (slack/post-message + :text (format "```\n%s```" snippet-text) + :channel-id channel-id)))) + +(provide 'slack-snippets) diff --git a/users/grfn/emacs.d/slack.el b/users/grfn/emacs.d/slack.el new file mode 100644 index 000000000000..54d3b40b099c --- /dev/null +++ b/users/grfn/emacs.d/slack.el @@ -0,0 +1,24 @@ +;;; -*- lexical-binding: t; -*- + +(after! slack + (set-face-foreground 'slack-message-output-header +solarized-s-base01) + (set-face-attribute 'slack-message-output-header nil :underline nil) + (set-face-attribute 'slack-message-output-text nil :height 1.0)) + +(require 'slack) +(setq slack-buffer-emojify 't + slack-prefer-current-team 't + slack-thread-also-send-to-room nil) + +(set-popup-rule! "^\\*Slack" + :quit nil + :select t + :side 'bottom + :ttl nil + :size 0.5) + +(add-hook #'slack-message-buffer-mode-hook + (lambda () (toggle-truncate-lines -1))) + +(map! (:map slack-message-buffer-mode-map + :n "q" #'delete-window)) diff --git a/users/grfn/emacs.d/snippets/haskell-mode/annotation b/users/grfn/emacs.d/snippets/haskell-mode/annotation new file mode 100644 index 000000000000..8a2854d759df --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/annotation @@ -0,0 +1,5 @@ +# key: ann +# name: annotation +# expand-env: ((yas-indent-line 'fixed)) +# -- +{-# ANN ${1:module} ("${2:HLint: ignore ${3:Reduce duplication}}" :: String) #-} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/benchmark-module b/users/grfn/emacs.d/snippets/haskell-mode/benchmark-module new file mode 100644 index 000000000000..cbb1646e41d1 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/benchmark-module @@ -0,0 +1,26 @@ +# key: bench +# name: benchmark-module +# expand-env: ((yas-indent-line (quote fixed))) +# -- +-------------------------------------------------------------------------------- +module ${1:`(if (not buffer-file-name) "Module" + (let ((name (file-name-sans-extension (buffer-file-name))) + (case-fold-search nil)) + (if (cl-search "bench/" name) + (replace-regexp-in-string "/" "." + (replace-regexp-in-string "^\/[^A-Z]*" "" + (car (last (split-string name "src"))))) + (file-name-nondirectory name))))`} ( benchmark, main ) where +-------------------------------------------------------------------------------- +import Bench.Prelude +-------------------------------------------------------------------------------- +import ${1:$(s-chop-suffix "Bench" yas-text)} +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain [benchmark] + +-------------------------------------------------------------------------------- + +benchmark :: Benchmark +benchmark = bgroup "${1:$(->> yas-text (s-chop-suffix "Bench") (s-split ".") -last-item)}" [bench "something dumb" $ nf (1 +) (1 :: Int)] diff --git a/users/grfn/emacs.d/snippets/haskell-mode/header b/users/grfn/emacs.d/snippets/haskell-mode/header new file mode 100644 index 000000000000..fdd8250d86ca --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/header @@ -0,0 +1,5 @@ +# key: hh +# name: header +# expand-env: ((yas-indent-line 'fixed)) +# -- +--------------------------------------------------------------------------------$2 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-generator b/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-generator new file mode 100644 index 000000000000..68863f70542b --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-generator @@ -0,0 +1,8 @@ +# key: gen +# name: Hedgehog Generator +# expand-env: ((yas-indent-line (quote fixed))) +# -- +gen${1:Foo} :: Gen $1 +gen$1 = do + $2 + pure $1{..} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-property b/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-property new file mode 100644 index 000000000000..bf39a2a3eecb --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/hedgehog-property @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Hedgehog Property +# key: hprop +# expand-env: ((yas-indent-line 'fixed)) +# -- +hprop_${1:somethingIsAlwaysTrue} :: Property +hprop_$1 = property $ do + ${2:x} <- forAll ${3:Gen.int $ Range.linear 1 100} + ${4:x === x} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/hlint b/users/grfn/emacs.d/snippets/haskell-mode/hlint new file mode 100644 index 000000000000..74b63dc672e4 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/hlint @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: hlint +# uuid: +# expand-env: ((yas-indent-line 'fixed)) +# key: hlint +# condition: t +# -- +{-# ANN module ("Hlint: ignore $1" :: String) #- } \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/import-i b/users/grfn/emacs.d/snippets/haskell-mode/import-i new file mode 100644 index 000000000000..4a7fca2c2fd6 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/import-i @@ -0,0 +1,4 @@ +# key: i +# name: import-i +# -- +import ${1:Prelude} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/inl b/users/grfn/emacs.d/snippets/haskell-mode/inl new file mode 100644 index 000000000000..6e17b83d7114 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/inl @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: inl +# key: inl +# expand-env: ((yas-indent-line 'fixed)) +# -- +{-# INLINE $1 #-} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/inline b/users/grfn/emacs.d/snippets/haskell-mode/inline new file mode 100644 index 000000000000..1beafbe50b56 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/inline @@ -0,0 +1,5 @@ +# key: inline +# name: inline +# expand-env: ((yas-indent-line 'fixed)) +# -- +{-# INLINE $1 #-} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/language pragma b/users/grfn/emacs.d/snippets/haskell-mode/language pragma new file mode 100644 index 000000000000..6f84720f4511 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/language pragma @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: language pragma +# key: lang +# expand-env: ((yas-indent-line 'fixed)) +# -- +{-# LANGUAGE $1 #-} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/lens.field b/users/grfn/emacs.d/snippets/haskell-mode/lens.field new file mode 100644 index 000000000000..b22ea3d2e888 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/lens.field @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: lens.field +# key: lens +# expand-env: ((yas-indent-line 'fixed)) +# -- +${1:field} :: Lens' ${2:Source} ${3:Target} +$1 = lens _${4:sourceField} $ \\${2:$(-> yas-text s-word-initials s-downcase)} ${4:$(-> yas-text s-word-initials s-downcase)} -> ${2:$(-> yas-text s-word-initials s-downcase)} { _$4 = ${4:$(-> yas-text s-word-initials s-downcase)} } \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/module b/users/grfn/emacs.d/snippets/haskell-mode/module new file mode 100644 index 000000000000..4554d33f9ba7 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/module @@ -0,0 +1,32 @@ +# -*- mode: snippet -*- +# key: module +# name: module +# condition: (= (length "module") (current-column)) +# expand-env: ((yas-indent-line 'fixed)) +# contributor: Luke Hoersten <luke@hoersten.org> +# -- +-------------------------------------------------------------------------------- +-- | +-- Module : $1 +-- Description : $2 +-- Maintainer : Griffin Smith <grfn@urbint.com> +-- Maturity : ${3:Draft, Usable, Maintained, OR MatureAF} +-- +-- $4 +-------------------------------------------------------------------------------- +module ${1:`(if (not buffer-file-name) "Module" + (let ((name (file-name-sans-extension (buffer-file-name))) + (case-fold-search nil)) + (if (or (cl-search "src/" name) + (cl-search "test/" name)) + (replace-regexp-in-string "/" "." + (replace-regexp-in-string "^\/[^A-Z]*" "" + (car (last (split-string name "src"))))) + (file-name-nondirectory name))))`} + ( + ) where +-------------------------------------------------------------------------------- +import Prelude +-------------------------------------------------------------------------------- + +$0 diff --git a/users/grfn/emacs.d/snippets/haskell-mode/shut up, hlint b/users/grfn/emacs.d/snippets/haskell-mode/shut up, hlint new file mode 100644 index 000000000000..fccff1d66f29 --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/shut up, hlint @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: shut up, hlint +# key: dupl +# expand-env: ((yas-indent-line 'fixed)) +# -- +{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/test-group b/users/grfn/emacs.d/snippets/haskell-mode/test-group new file mode 100644 index 000000000000..948e90d9e02e --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/test-group @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: test-group +# uuid: +# key: testGroup +# condition: t +# -- +testGroup "${1:name}" +[ $0 +] \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/test-module b/users/grfn/emacs.d/snippets/haskell-mode/test-module new file mode 100644 index 000000000000..036b0ae9983a --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/test-module @@ -0,0 +1,27 @@ +# -*- mode: snippet -*- +# name: test-module +# key: test +# expand-env: ((yas-indent-line 'fixed)) +# -- +-------------------------------------------------------------------------------- +module ${1:`(if (not buffer-file-name) "Module" + (let ((name (file-name-sans-extension (buffer-file-name))) + (case-fold-search nil)) + (if (cl-search "test/" name) + (replace-regexp-in-string "/" "." + (replace-regexp-in-string "^\/[^A-Z]*" "" + (car (last (split-string name "src"))))) + (file-name-nondirectory name))))`} (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import ${1:$(s-chop-suffix "Spec" yas-text)} +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "$1" + [ $0 + ] \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/haskell-mode/undefined b/users/grfn/emacs.d/snippets/haskell-mode/undefined new file mode 100644 index 000000000000..7bcd99b5716c --- /dev/null +++ b/users/grfn/emacs.d/snippets/haskell-mode/undefined @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: undefined +# key: u +# expand-env: ((yas-indent-line 'fixed) (yas-wrap-around-region 'nil)) +# -- +undefined$1 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/action-type b/users/grfn/emacs.d/snippets/js2-mode/action-type new file mode 100644 index 000000000000..ef8d1a3863ee --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/action-type @@ -0,0 +1,4 @@ +# key: at +# name: action-type +# -- +export const ${1:FOO_BAR$(->> yas-text s-upcase (s-replace-all '(("-" . "_") (" " . "_"))))}: '${3:ns}/${1:$(-> yas-text s-dashed-words)}' = '$3/${1:$(-> yas-text s-dashed-words)}'$5 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/before b/users/grfn/emacs.d/snippets/js2-mode/before new file mode 100644 index 000000000000..4569b6583143 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/before @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: before +# key: bef +# -- +before(function() { + $1 +}) diff --git a/users/grfn/emacs.d/snippets/js2-mode/context b/users/grfn/emacs.d/snippets/js2-mode/context new file mode 100644 index 000000000000..d83809f3c35e --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/context @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: context +# key: context +# -- +context('$1', function() { + $2 +}) diff --git a/users/grfn/emacs.d/snippets/js2-mode/describe b/users/grfn/emacs.d/snippets/js2-mode/describe new file mode 100644 index 000000000000..bd0198181d02 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/describe @@ -0,0 +1,6 @@ +# key: desc +# name: describe +# -- +describe('$1', () => { + $2 +}) \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/expect b/users/grfn/emacs.d/snippets/js2-mode/expect new file mode 100644 index 000000000000..eba41ef3309d --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/expect @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: expect +# key: ex +# -- +expect($1).$2 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/function b/users/grfn/emacs.d/snippets/js2-mode/function new file mode 100644 index 000000000000..b423044b4410 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/function @@ -0,0 +1,6 @@ +# key: f +# name: function +# -- +function $1($2) { + $3 +} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/header b/users/grfn/emacs.d/snippets/js2-mode/header new file mode 100644 index 000000000000..3e303764cb0b --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/header @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: header +# key: hh +# expand-env: ((yas-indent-line 'fixed)) +# -- +//////////////////////////////////////////////////////////////////////////////// diff --git a/users/grfn/emacs.d/snippets/js2-mode/it b/users/grfn/emacs.d/snippets/js2-mode/it new file mode 100644 index 000000000000..a451cfc08a90 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/it @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: it +# key: it +# -- +it('$1', () => { + $2 +}) \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/it-pending b/users/grfn/emacs.d/snippets/js2-mode/it-pending new file mode 100644 index 000000000000..00da312e1096 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/it-pending @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: it-pending +# key: xi +# -- +it('$1')$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/module b/users/grfn/emacs.d/snippets/js2-mode/module new file mode 100644 index 000000000000..dc79819d8979 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/module @@ -0,0 +1,12 @@ +# key: module +# name: module +# expand-env: ((yas-indent-line (quote fixed))) +# condition: (= (length "module") (current-column)) +# -- +/** + * @fileOverview $1 + * @name ${2:`(file-name-nondirectory (buffer-file-name))`} + * @author Griffin Smith + * @license Proprietary + */ +$3 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/record b/users/grfn/emacs.d/snippets/js2-mode/record new file mode 100644 index 000000000000..0bb0f024367b --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/record @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: record +# key: rec +# -- +export default class $1 extends Record({ + $2 +}) {} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/js2-mode/test b/users/grfn/emacs.d/snippets/js2-mode/test new file mode 100644 index 000000000000..938d490a74e8 --- /dev/null +++ b/users/grfn/emacs.d/snippets/js2-mode/test @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: test +# key: test +# -- +test('$1', () => { + $2 +}) \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/nix-mode/fetchFromGitHub b/users/grfn/emacs.d/snippets/nix-mode/fetchFromGitHub new file mode 100644 index 000000000000..9b9373573048 --- /dev/null +++ b/users/grfn/emacs.d/snippets/nix-mode/fetchFromGitHub @@ -0,0 +1,12 @@ +# -*- mode: snippet -*- +# name: fetchFromGitHub +# uuid: +# key: fetchFromGitHub +# condition: t +# -- +fetchFromGitHub { + owner = "$1"; + repo = "$2"; + rev = "$3"; + sha256 = "0000000000000000000000000000000000000000000000000000"; +} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/nix-mode/pythonPackage b/users/grfn/emacs.d/snippets/nix-mode/pythonPackage new file mode 100644 index 000000000000..0a74c21e1857 --- /dev/null +++ b/users/grfn/emacs.d/snippets/nix-mode/pythonPackage @@ -0,0 +1,16 @@ +# key: pypkg +# name: pythonPackage +# condition: t +# -- +${1:pname} = buildPythonPackage rec { + name = "\${pname}-\${version}"; + pname = "$1"; + version = "${2:1.0.0}"; + src = fetchPypi { + inherit pname version; + sha256 = "0000000000000000000000000000000000000000000000000000"; + }; + propagatedBuildInputs = with pythonSelf; [ + $3 + ]; +}; \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/nix-mode/sha256 b/users/grfn/emacs.d/snippets/nix-mode/sha256 new file mode 100644 index 000000000000..e3d52e1c0201 --- /dev/null +++ b/users/grfn/emacs.d/snippets/nix-mode/sha256 @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: sha256 +# uuid: +# key: sha256 +# condition: t +# -- +sha256 = "0000000000000000000000000000000000000000000000000000"; \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/SQL source block b/users/grfn/emacs.d/snippets/org-mode/SQL source block new file mode 100644 index 000000000000..b5d43fd6bc01 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/SQL source block @@ -0,0 +1,6 @@ +# key: sql +# name: SQL source block +# -- +#+BEGIN_SRC sql ${1::async} +$2 +#+END_SRC diff --git a/users/grfn/emacs.d/snippets/org-mode/combat b/users/grfn/emacs.d/snippets/org-mode/combat new file mode 100644 index 000000000000..ef46062d09b4 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/combat @@ -0,0 +1,13 @@ +# -*- mode: snippet -*- +# name: combat +# uuid: +# key: combat +# condition: t +# -- +| | initiative | max hp | current hp | status | | +|-------------+------------+--------+------------+--------+------| +| Barty Barty | | | | | <--- | +| Hectoroth | | | | | | +| Xanadu | | | | | | +| Aurora | | | | | | +| EFB | | | | | | \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/date b/users/grfn/emacs.d/snippets/org-mode/date new file mode 100644 index 000000000000..297529cdac64 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/date @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# key: date +# name: date.org +# -- +[`(format-time-string "%Y-%m-%d")`]$0 diff --git a/users/grfn/emacs.d/snippets/org-mode/date-time b/users/grfn/emacs.d/snippets/org-mode/date-time new file mode 100644 index 000000000000..fde469276c3f --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/date-time @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: date-time +# key: dt +# -- +[`(format-time-string "%Y-%m-%d %H:%m:%S")`] \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/description b/users/grfn/emacs.d/snippets/org-mode/description new file mode 100644 index 000000000000..a43bc95cc3ed --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/description @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: description +# key: desc +# -- +:DESCRIPTION: +$1 +:END: diff --git a/users/grfn/emacs.d/snippets/org-mode/nologdone b/users/grfn/emacs.d/snippets/org-mode/nologdone new file mode 100644 index 000000000000..e5be85d6b3c0 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/nologdone @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: nologdone +# key: nologdone +# -- +#+STARTUP: nologdone$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/python source block b/users/grfn/emacs.d/snippets/org-mode/python source block new file mode 100644 index 000000000000..247ae51b0b78 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/python source block @@ -0,0 +1,6 @@ +# key: py +# name: Python source block +# -- +#+BEGIN_SRC python +$0 +#+END_SRC \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/reveal b/users/grfn/emacs.d/snippets/org-mode/reveal new file mode 100644 index 000000000000..1bdbdfa5dc36 --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/reveal @@ -0,0 +1,6 @@ +# key: reveal +# name: reveal +# condition: t +# -- +#+ATTR_REVEAL: :frag ${1:roll-in} +$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/org-mode/transaction b/users/grfn/emacs.d/snippets/org-mode/transaction new file mode 100644 index 000000000000..37f2dd31caff --- /dev/null +++ b/users/grfn/emacs.d/snippets/org-mode/transaction @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: transaction +# key: begin +# -- +BEGIN; +$0 +ROLLBACK; \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/prolog-mode/use-module b/users/grfn/emacs.d/snippets/prolog-mode/use-module new file mode 100644 index 000000000000..970391f93693 --- /dev/null +++ b/users/grfn/emacs.d/snippets/prolog-mode/use-module @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: use-module +# uuid: +# key: use +# condition: t +# -- +:- use_module(${1:library($2)}${3:, [$4]}). \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/python-mode/add_column b/users/grfn/emacs.d/snippets/python-mode/add_column new file mode 100644 index 000000000000..47e83850d5b7 --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/add_column @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: add_column +# key: op.add_column +# -- +op.add_column('${1:table}', sa.Column('${2:name}', sa.${3:String()}))$0 diff --git a/users/grfn/emacs.d/snippets/python-mode/decorate b/users/grfn/emacs.d/snippets/python-mode/decorate new file mode 100644 index 000000000000..9448b45c9623 --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/decorate @@ -0,0 +1,15 @@ +# -*- mode: snippet -*- +# name: decorate +# uuid: +# key: decorate +# condition: t +# -- +def wrap(inner): + @wraps(inner) + def wrapped(*args, **kwargs): + ret = inner(*args, **kwargs) + return ret + + return wrapped + +return wrap \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/python-mode/dunder b/users/grfn/emacs.d/snippets/python-mode/dunder new file mode 100644 index 000000000000..c49ec40a15cc --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/dunder @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: dunder +# uuid: +# key: du +# condition: t +# -- +__$1__$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/python-mode/name b/users/grfn/emacs.d/snippets/python-mode/name new file mode 100644 index 000000000000..eca6d60b481f --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/name @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: name +# uuid: +# key: name +# condition: t +# -- +__name__ \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/python-mode/op.get_bind.execute b/users/grfn/emacs.d/snippets/python-mode/op.get_bind.execute new file mode 100644 index 000000000000..aba801c6baf9 --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/op.get_bind.execute @@ -0,0 +1,7 @@ +# key: exec +# name: op.get_bind.execute +# -- +op.get_bind().execute( + """ + `(progn (sqlup-mode) "")`$1 + """) diff --git a/users/grfn/emacs.d/snippets/python-mode/pdb b/users/grfn/emacs.d/snippets/python-mode/pdb new file mode 100644 index 000000000000..6b5c0bbc0a73 --- /dev/null +++ b/users/grfn/emacs.d/snippets/python-mode/pdb @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: pdb +# uuid: +# key: pdb +# condition: t +# -- +import pdb; pdb.set_trace() \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/rust-mode/#[macro_use] b/users/grfn/emacs.d/snippets/rust-mode/#[macro_use] new file mode 100644 index 000000000000..fea942a337f6 --- /dev/null +++ b/users/grfn/emacs.d/snippets/rust-mode/#[macro_use] @@ -0,0 +1,5 @@ +# key: macro_use +# name: #[macro_use] +# -- +#[macro_use] +${1:extern crate} ${2:something};$0 diff --git a/users/grfn/emacs.d/snippets/rust-mode/async test b/users/grfn/emacs.d/snippets/rust-mode/async test new file mode 100644 index 000000000000..27410754742a --- /dev/null +++ b/users/grfn/emacs.d/snippets/rust-mode/async test @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: async test +# uuid: +# key: atest +# condition: t +# -- +#[tokio::test${1:(flavor = "multi_thread")}] +async fn ${2:test_name}() { + `%`$0 +} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/rust-mode/benchmark b/users/grfn/emacs.d/snippets/rust-mode/benchmark new file mode 100644 index 000000000000..f1446923a0e4 --- /dev/null +++ b/users/grfn/emacs.d/snippets/rust-mode/benchmark @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: benchmark +# uuid: +# key: bench +# condition: t +# -- +#[bench] +fn ${1:benchmark_name}(b: &mut Bencher) { + `%`b.iter(|| $0); +} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/rust-mode/proptest b/users/grfn/emacs.d/snippets/rust-mode/proptest new file mode 100644 index 000000000000..377b3cfcf60c --- /dev/null +++ b/users/grfn/emacs.d/snippets/rust-mode/proptest @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: proptest +# uuid: +# key: proptest +# condition: t +# -- +#[proptest] +fn ${1:test_name}($2) { + `%`$0 +} \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/rust-mode/tests b/users/grfn/emacs.d/snippets/rust-mode/tests new file mode 100644 index 000000000000..0a476ab58661 --- /dev/null +++ b/users/grfn/emacs.d/snippets/rust-mode/tests @@ -0,0 +1,9 @@ +# key: tests +# name: test module +# -- +#[cfg(test)] +mod ${1:tests} { + use super::*; + + $0 +} diff --git a/users/grfn/emacs.d/snippets/snippet-mode/indent b/users/grfn/emacs.d/snippets/snippet-mode/indent new file mode 100644 index 000000000000..d38ffceafbad --- /dev/null +++ b/users/grfn/emacs.d/snippets/snippet-mode/indent @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: indent +# key: indent +# -- +# expand-env: ((yas-indent-line 'fixed)) \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/sql-mode/count(*) group by b/users/grfn/emacs.d/snippets/sql-mode/count(*) group by new file mode 100644 index 000000000000..6acc46ff397a --- /dev/null +++ b/users/grfn/emacs.d/snippets/sql-mode/count(*) group by @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: count(*) group by +# key: countby +# -- +SELECT count(*), ${1:column} FROM ${2:table} GROUP BY $1; diff --git a/users/grfn/emacs.d/snippets/terraform-mode/variable b/users/grfn/emacs.d/snippets/terraform-mode/variable new file mode 100644 index 000000000000..e64175200f44 --- /dev/null +++ b/users/grfn/emacs.d/snippets/terraform-mode/variable @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: variable +# uuid: +# key: var +# condition: t +# -- +variable "${1:name}" { + type = ${2:string} + ${3:default = ${4:default}} +} +$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/snippets/text-mode/date b/users/grfn/emacs.d/snippets/text-mode/date new file mode 100644 index 000000000000..7b9431147011 --- /dev/null +++ b/users/grfn/emacs.d/snippets/text-mode/date @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# name: date +# key: date +# -- +`(format-time-string "%Y-%m-%d")`$0 \ No newline at end of file diff --git a/users/grfn/emacs.d/splitjoin.el b/users/grfn/emacs.d/splitjoin.el new file mode 100644 index 000000000000..dbc9704d7909 --- /dev/null +++ b/users/grfn/emacs.d/splitjoin.el @@ -0,0 +1,192 @@ +;;; -*- lexical-binding: t; -*- + +(require 'dash) +(load! "utils") + +;;; +;;; Vars +;;; + +(defvar +splitjoin/split-callbacks '() + "Alist mapping major mode symbol names to lists of split callbacks") + +(defvar +splitjoin/join-callbacks '() + "Alist mapping major mode symbol names to lists of join callbacks") + + + +;;; +;;; Definition macros +;;; + +(defmacro +splitjoin/defsplit (mode name &rest body) + `(setf + (alist-get ',name (alist-get ,mode +splitjoin/split-callbacks)) + (ฮป! () ,@body))) + +(defmacro +splitjoin/defjoin (mode name &rest body) + `(setf + (alist-get ',name (alist-get ,mode +splitjoin/join-callbacks)) + (ฮป! () ,@body))) + +;;; +;;; Commands +;;; + +(defun +splitjoin/split () + (interactive) + (when-let (callbacks (->> +splitjoin/split-callbacks + (alist-get major-mode) + (-map #'cdr))) + (find-if #'funcall callbacks))) + +(defun +splitjoin/join () + (interactive) + (when-let (callbacks (->> +splitjoin/join-callbacks + (alist-get major-mode) + (-map #'cdr))) + (find-if #'funcall callbacks))) + + +;;; +;;; Splits and joins +;;; TODO: this should probably go in a file-per-language +;;; + +(+splitjoin/defjoin + 'elixir-mode + join-do + (let* ((function-pattern (rx (and (zero-or-more whitespace) + "do" + (zero-or-more whitespace) + (optional (and "#" (zero-or-more anything))) + eol))) + (end-pattern (rx bol + (zero-or-more whitespace) + "end" + (zero-or-more whitespace) + eol)) + (else-pattern (rx bol + (zero-or-more whitespace) + "else" + (zero-or-more whitespace) + eol)) + (lineno (line-number-at-pos)) + (line (thing-at-point 'line t))) + (when-let ((do-start-pos (string-match function-pattern line))) + (cond + ((string-match-p end-pattern (get-line (inc lineno))) + (modify-then-indent + (goto-line-char do-start-pos) + (insert ",") + (goto-char (line-end-position)) + (insert ": nil") + (line-move 1) + (delete-line)) + t) + + ((string-match-p end-pattern (get-line (+ 2 lineno))) + (modify-then-indent + (goto-line-char do-start-pos) + (insert ",") + (goto-char (line-end-position)) + (insert ":") + (join-line t) + (line-move 1) + (delete-line)) + t) + + ((and (string-match-p else-pattern (get-line (+ 2 lineno))) + (string-match-p end-pattern (get-line (+ 4 lineno)))) + (modify-then-indent + (goto-line-char do-start-pos) + (insert ",") + (goto-char (line-end-position)) + (insert ":") + (join-line t) + (goto-eol) + (insert ",") + (join-line t) + (goto-eol) + (insert ":") + (join-line t) + (line-move 1) + (delete-line)) + t))))) + +(comment + (string-match (rx (and bol + "if " + (one-or-more anything) + "," + (zero-or-more whitespace) + "do:" + (one-or-more anything) + "," + (zero-or-more whitespace) + "else:" + (one-or-more anything))) + "if 1, do: nil, else: nil") + + ) + +(+splitjoin/defsplit + 'elixir-mode + split-do-with-optional-else + (let* ((if-with-else-pattern (rx (and bol + (one-or-more anything) + "," + (zero-or-more whitespace) + "do:" + (one-or-more anything) + (optional + "," + (zero-or-more whitespace) + "else:" + (one-or-more anything))))) + (current-line (get-line))) + (when (string-match if-with-else-pattern current-line) + (modify-then-indent + (assert (goto-regex-on-line ",[[:space:]]*do:")) + (delete-char 1) + (assert (goto-regex-on-line ":")) + (delete-char 1) + (insert "\n") + (when (goto-regex-on-line-r ",[[:space:]]*else:") + (delete-char 1) + (insert "\n") + (assert (goto-regex-on-line ":")) + (delete-char 1) + (insert "\n")) + (goto-eol) + (insert "\nend")) + t))) + +(comment + (+splitjoin/defsplit 'elixir-mode split-def + (let ((function-pattern (rx (and "," + (zero-or-more whitespace) + "do:"))) + (line (thing-at-point 'line t))) + (when-let (idx (string-match function-pattern line)) + (let ((beg (line-beginning-position)) + (orig-line-char (- (point) (line-beginning-position)))) + (save-mark-and-excursion + (goto-line-char idx) + (delete-char 1) + (goto-line-char (string-match ":" (thing-at-point 'line t))) + (delete-char 1) + (insert "\n") + (goto-eol) + (insert "\n") + (insert "end") + (evil-indent beg (+ (line-end-position) 1)))) + (goto-line-char orig-line-char) + t)))) + +(+splitjoin/defjoin + 'elixir-mode + join-if-with-else + (let* ((current-line (thing-at-point 'line))))) + +(provide 'splitjoin) diff --git a/users/grfn/emacs.d/sql-strings.el b/users/grfn/emacs.d/sql-strings.el new file mode 100644 index 000000000000..eef397a24ea6 --- /dev/null +++ b/users/grfn/emacs.d/sql-strings.el @@ -0,0 +1,75 @@ +;;; -*- lexical-binding: t; -*- + +;;; https://www.emacswiki.org/emacs/StringAtPoint +(defun ourcomments-string-or-comment-bounds-1 (what) + (save-restriction + (widen) + (let* ((here (point)) + ;; Fix-me: when on end-point, how to handle that and which should be last hit point? + (state (parse-partial-sexp (point-min) (1+ here))) + (type (if (nth 3 state) + 'string + (if (nth 4 state) + 'comment))) + (start (when type (nth 8 state))) + end) + (unless start + (setq state (parse-partial-sexp (point-min) here)) + (setq type (if (nth 3 state) + 'string + (if (nth 4 state) + 'comment))) + (setq start (when type (nth 8 state)))) + (unless (or (not what) + (eq what type)) + (setq start nil)) + (if (not start) + (progn + (goto-char here) + nil) + (setq state (parse-partial-sexp (1+ start) (point-max) + nil nil state 'syntax-table)) + (setq end (point)) + (goto-char here) + (cons start end))))) + +(defun ourcomments-bounds-of-string-at-point () + "Return bounds of string at point if any." + (ourcomments-string-or-comment-bounds-1 'string)) + +(put 'string 'bounds-of-thing-at-point 'ourcomments-bounds-of-string-at-point) + +(defun -sanitize-sql-string (str) + (->> str + (downcase) + (s-trim) + (replace-regexp-in-string + (rx (or (and string-start (or "\"\"\"" + "\"")) + (and (or "\"\"\"" + "\"") + string-end))) + "") + (s-trim))) + +(defun sql-string-p (str) + "Returns 't if STR looks like a string literal for a SQL statement" + (setq str (-sanitize-sql-string str)) + (or (s-starts-with? "select" str))) + +;;; tests + +(require 'ert) + +(ert-deftest sanitize-sql-string-test () + (should (string-equal "select * from foo;" + (-sanitize-sql-string + "\"\"\"SELECT * FROM foo;\n\n\"\"\"")))) + +(ert-deftest test-sql-string-p () + (dolist (str '("SELECT * FROM foo;" + "select * from foo;")) + (should (sql-string-p str))) + + (dolist (str '("not a QUERY")) + (should-not (sql-string-p str)))) diff --git a/users/grfn/emacs.d/terraform.el b/users/grfn/emacs.d/terraform.el new file mode 100644 index 000000000000..2d69c9bad9db --- /dev/null +++ b/users/grfn/emacs.d/terraform.el @@ -0,0 +1,31 @@ +;;; -*- lexical-binding: t; -*- + +(add-hook 'terraform-mode-hook #'terraform-format-on-save-mode) + +(defun packer-format-buffer () + (interactive) + (let ((buf (get-buffer-create "*packer-fmt*"))) + (if (zerop (call-process-region (point-min) (point-max) + "packer" nil buf nil "fmt" "-")) + (let ((point (point)) + (window-start (window-start))) + (erase-buffer) + (insert-buffer-substring buf) + (goto-char point) + (set-window-start nil window-start)) + (message "packer fmt failed: %s" (with-current-buffer buf (buffer-string)))) + (kill-buffer buf))) + +(define-minor-mode packer-format-on-save-mode + "Run packer-format-buffer before saving the current buffer" + :lighter nil + (if packer-format-on-save-mode + (add-hook 'before-save-hook #'packer-format-buffer nil t) + (remove-hook 'before-save-hook #'packer-format-buffer t))) + +(defun maybe-init-packer () + (interactive) + (when (s-ends-with-p ".pkr" (file-name-base (buffer-file-name))) + (packer-format-on-save-mode))) + +(add-hook 'hcl-mode-hook #'maybe-init-packer) diff --git a/users/grfn/emacs.d/tests/splitjoin_test.el b/users/grfn/emacs.d/tests/splitjoin_test.el new file mode 100644 index 000000000000..6495a1a5952e --- /dev/null +++ b/users/grfn/emacs.d/tests/splitjoin_test.el @@ -0,0 +1,68 @@ +;;; private/grfn/tests/splitjoin_test.el -*- lexical-binding: t; -*- + +(require 'ert) +;; (load! 'splitjoin) +;; (load! 'utils) +; (require 'splitjoin) + +;;; Helpers + +(defvar *test-buffer* nil) +(make-variable-buffer-local '*test-buffer*) + +(defun test-buffer () + (when (not *test-buffer*) + (setq *test-buffer* (get-buffer-create "test-buffer"))) + *test-buffer*) + +(defmacro with-test-buffer (&rest body) + `(with-current-buffer (test-buffer) + ,@body)) + +(defun set-test-buffer-mode (mode) + (let ((mode (if (functionp mode) mode + (-> mode symbol-name (concat "-mode") intern)))) + (assert (functionp mode)) + (with-test-buffer (funcall mode)))) + +(defmacro set-test-buffer-contents (contents) + (with-test-buffer + (erase-buffer) + (insert contents))) + +(defun test-buffer-contents () + (with-test-buffer (substring-no-properties (buffer-string)))) + +(defmacro assert-test-buffer-contents (expected-contents) + `(should (equal (string-trim (test-buffer-contents)) + (string-trim ,expected-contents)))) + +(defmacro should-join-to (mode original-contents expected-contents) + `(progn + (set-test-buffer-mode ,mode) + (set-test-buffer-contents ,original-contents) + (with-test-buffer (+splitjoin/join)) + (assert-test-buffer-contents ,expected-contents))) + +(defmacro should-split-to (mode original-contents expected-contents) + `(progn + (set-test-buffer-mode ,mode) + (set-test-buffer-contents ,original-contents) + (with-test-buffer (+splitjoin/split)) + (assert-test-buffer-contents ,expected-contents))) + +(defmacro should-splitjoin (mode joined-contents split-contents) + `(progn + (should-split-to ,mode ,joined-contents ,split-contents) + (should-join-to ,mode ,split-contents ,joined-contents))) + +;;; Tests + +;; Elixir +(ert-deftest elixir-if-splitjoin-test () + (should-splitjoin 'elixir + "if predicate?(), do: result" + "if predicate?() do + result +end")) + diff --git a/users/grfn/emacs.d/themes/grfn-solarized-light-theme.el b/users/grfn/emacs.d/themes/grfn-solarized-light-theme.el new file mode 100644 index 000000000000..ae00b6b5fc75 --- /dev/null +++ b/users/grfn/emacs.d/themes/grfn-solarized-light-theme.el @@ -0,0 +1,115 @@ +(require 'solarized) +(eval-when-compile + (require 'solarized-palettes)) + +;; (defun grfn-solarized-theme () +;; (custom-theme-set-faces +;; theme-name +;; `(font-lock-doc-face ((,class (:foreground ,s-base1)))) +;; `(font-lock-preprocessor-face ((,class (:foreground ,red)))) +;; `(font-lock-keyword-face ((,class (:foreground ,green)))) + +;; `(elixir-attribute-face ((,class (:foreground ,blue)))) +;; `(elixir-atom-face ((,class (:foreground ,cyan)))))) + +(setq +solarized-s-base03 "#002b36" + +solarized-s-base02 "#073642" + ;; emphasized content + +solarized-s-base01 "#586e75" + ;; primary content + +solarized-s-base00 "#657b83" + +solarized-s-base0 "#839496" + ;; comments + +solarized-s-base1 "#93a1a1" + ;; background highlight light + +solarized-s-base2 "#eee8d5" + ;; background light + +solarized-s-base3 "#fdf6e3" + + ;; Solarized accented colors + +solarized-yellow "#b58900" + +solarized-orange "#cb4b16" + +solarized-red "#dc322f" + +solarized-magenta "#d33682" + +solarized-violet "#6c71c4" + +solarized-blue "#268bd2" + +solarized-cyan "#2aa198" + +solarized-green "#859900" + + ;; Darker and lighter accented colors + ;; Only use these in exceptional circumstances! + +solarized-yellow-d "#7B6000" + +solarized-yellow-l "#DEB542" + +solarized-orange-d "#8B2C02" + +solarized-orange-l "#F2804F" + +solarized-red-d "#990A1B" + +solarized-red-l "#FF6E64" + +solarized-magenta-d "#93115C" + +solarized-magenta-l "#F771AC" + +solarized-violet-d "#3F4D91" + +solarized-violet-l "#9EA0E5" + +solarized-blue-d "#00629D" + +solarized-blue-l "#69B7F0" + +solarized-cyan-d "#00736F" + +solarized-cyan-l "#69CABF" + +solarized-green-d "#546E00" + +solarized-green-l "#B4C342") + + +(deftheme grfn-solarized-light "The light variant of Griffin's solarized theme") + +(setq grfn-solarized-faces + '("Griffin's solarized theme customization" + (custom-theme-set-faces + theme-name + `(font-lock-doc-face ((t (:foreground ,+solarized-s-base1)))) + `(font-lock-preprocessor-face ((t (:foreground ,+solarized-red)))) + `(font-lock-keyword-face ((t (:foreground ,+solarized-green)))) + + `(elixir-attribute-face ((t (:foreground ,+solarized-blue)))) + `(elixir-atom-face ((t (:foreground ,+solarized-cyan)))) + `(agda2-highlight-keyword-face ((t (:foreground ,green)))) + `(agda2-highlight-string-face ((t (:foreground ,cyan)))) + `(agda2-highlight-number-face ((t (:foreground ,violet)))) + `(agda2-highlight-symbol-face ((((background ,base3)) (:foreground ,base01)))) + `(agda2-highlight-primitive-type-face ((t (:foreground ,blue)))) + `(agda2-highlight-bound-variable-face ((t nil))) + `(agda2-highlight-inductive-constructor-face ((t (:foreground ,green)))) + `(agda2-highlight-coinductive-constructor-face ((t (:foreground ,yellow)))) + `(agda2-highlight-datatype-face ((t (:foreground ,blue)))) + `(agda2-highlight-field-face ((t (:foreground ,red)))) + `(agda2-highlight-function-face ((t (:foreground ,blue)))) + `(agda2-highlight-module-face ((t (:foreground ,yellow)))) + `(agda2-highlight-postulate-face ((t (:foreground ,blue)))) + `(agda2-highlight-primitive-face ((t (:foreground ,blue)))) + `(agda2-highlight-record-face ((t (:foreground ,blue)))) + `(agda2-highlight-dotted-face ((t nil))) + `(agda2-highlight-operator-face ((t nil))) + `(agda2-highlight-error-face ((t (:foreground ,red :underline t)))) + `(agda2-highlight-unsolved-meta-face ((t (:background ,base2)))) + `(agda2-highlight-unsolved-constraint-face ((t (:background ,base2)))) + `(agda2-highlight-termination-problem-face ((t (:background ,orange :foreground ,base03)))) + `(agda2-highlight-incomplete-pattern-face ((t (:background ,orange :foreground ,base03)))) + `(agda2-highlight-typechecks-face ((t (:background ,cyan :foreground ,base03)))) + + `(font-lock-doc-face ((t (:foreground ,+solarized-s-base1)))) + `(font-lock-preprocessor-face ((t (:foreground ,+solarized-red)))) + `(font-lock-keyword-face ((t (:foreground ,+solarized-green :bold nil)))) + `(font-lock-builtin-face ((t (:foreground ,+solarized-s-base01 + :bold t)))) + + `(elixir-attribute-face ((t (:foreground ,+solarized-blue)))) + `(elixir-atom-face ((t (:foreground ,+solarized-cyan)))) + `(linum ((t (:background ,+solarized-s-base2 :foreground ,+solarized-s-base1)))) + `(line-number ((t (:background ,+solarized-s-base2 :foreground ,+solarized-s-base1)))) + + `(haskell-operator-face ((t (:foreground ,+solarized-green)))) + `(haskell-keyword-face ((t (:foreground ,+solarized-cyan)))) + + `(org-drawer ((t (:foreground ,+solarized-s-base1 + :bold t))))))) + +(solarized-with-color-variables + 'light 'grfn-solarized-light solarized-light-color-palette-alist) + +(provide-theme 'grfn-solarized-light) diff --git a/users/grfn/emacs.d/utils.el b/users/grfn/emacs.d/utils.el new file mode 100644 index 000000000000..21192753a268 --- /dev/null +++ b/users/grfn/emacs.d/utils.el @@ -0,0 +1,114 @@ +;;; -*- lexical-binding: t; -*- + + +;; Elisp Extras + +(defmacro comment (&rest _body) + "Comment out one or more s-expressions" + nil) + +(defun inc (x) "Returns x + 1" (+ 1 x)) +(defun dec (x) "Returns x - 1" (- x 1)) + +(defun average (ns) + "Arithmetic mean of xs" + (if (null ns) nil + (/ (apply #'+ ns) + (length ns)))) + +(comment + (average (list 1 2 3 4)) + ) + +;; +;; Text editing utils +;; + +;; Reading strings + +(defun get-char (&optional point) + "Get the character at the given `point' (defaulting to the current point), +without properties" + (let ((point (or point (point)))) + (buffer-substring-no-properties point (+ 1 point)))) + +(defun get-line (&optional lineno) + "Read the line number `lineno', or the current line if `lineno' is nil, and +return it as a string stripped of all text properties" + (let ((current-line (line-number-at-pos))) + (if (or (not lineno) + (= current-line lineno)) + (thing-at-point 'line t) + (save-mark-and-excursion + (line-move (- lineno (line-number-at-pos))) + (thing-at-point 'line t))))) + +(defun get-line-point () + "Get the position in the current line of the point" + (- (point) (line-beginning-position))) + +;; Moving in the file + +(defun goto-line-char (pt) + "Moves the point to the given position expressed as an offset from the start +of the line" + (goto-char (+ (line-beginning-position) pt))) + +(defun goto-eol () + "Moves to the end of the current line" + (goto-char (line-end-position))) + +(defun goto-regex-on-line (regex) + "Moves the point to the first occurrence of `regex' on the current line. +Returns nil if the regex did not match, non-nil otherwise" + (when-let ((current-line (get-line)) + (line-char (string-match regex current-line))) + (goto-line-char line-char))) + +(defun goto-regex-on-line-r (regex) + "Moves the point to the *last* occurrence of `regex' on the current line. +Returns nil if the regex did not match, non-nil otherwise" + (when-let ((current-line (get-line)) + (modified-regex (concat ".*\\(" regex "\\)")) + (_ (string-match modified-regex current-line)) + (match-start (match-beginning 1))) + (goto-line-char match-start))) + +(comment + (progn + (string-match (rx (and (zero-or-more anything) + (group "foo" "foo"))) + "foofoofoo") + (match-beginning 1))) + +;; Changing file contents + +(defun delete-line () + "Remove the line at the current point" + (delete-region (line-beginning-position) + (inc (line-end-position)))) + +(defmacro modify-then-indent (&rest body) + "Modify text in the buffer according to body, then re-indent from where the + cursor started to where the cursor ended up, then return the cursor to where + it started." + `(let ((beg (line-beginning-position)) + (orig-line-char (- (point) (line-beginning-position)))) + (atomic-change-group + (save-mark-and-excursion + ,@body + (evil-indent beg (+ (line-end-position) 1)))) + (goto-line-char orig-line-char))) + +(pcase-defmacro s-starts-with (prefix) + `(pred (s-starts-with-p ,prefix))) + +(pcase-defmacro s-contains (needle &optional ignore-case) + `(pred (s-contains-p ,needle + ,@(when ignore-case (list ignore-case))))) + +(comment + (pcase "foo" + ((s-contains "bar") 1) + ((s-contains "o") 2)) + ) diff --git a/users/grfn/emacs.d/vterm.el b/users/grfn/emacs.d/vterm.el new file mode 100644 index 000000000000..a7fdea46da32 --- /dev/null +++ b/users/grfn/emacs.d/vterm.el @@ -0,0 +1,24 @@ +;;; -*- lexical-binding: t; -*- + +(defun require-vterm () + (add-to-list + 'load-path + (concat + (s-trim + (shell-command-to-string + "nix-build --no-out-link ~/code/depot -A third_party.emacs.vterm")) + "/share/emacs/site-lisp/elpa/vterm-20200515.1412")) + (require 'vterm)) + +(defun +grfn/vterm-setup () + (hide-mode-line-mode) + (setq-local evil-collection-vterm-send-escape-to-vterm-p t)) + +(add-hook 'vterm-mode-hook #'+grfn/vterm-setup) + +(map! (:map vterm-mode-map + "<C-escape>" #'evil-normal-state)) + +(comment + (require-vterm) + ) diff --git a/users/grfn/gws.fyi/.envrc b/users/grfn/gws.fyi/.envrc new file mode 100644 index 000000000000..be81feddb1a5 --- /dev/null +++ b/users/grfn/gws.fyi/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" \ No newline at end of file diff --git a/users/grfn/gws.fyi/.gitignore b/users/grfn/gws.fyi/.gitignore new file mode 100644 index 000000000000..2b72eaed2988 --- /dev/null +++ b/users/grfn/gws.fyi/.gitignore @@ -0,0 +1,3 @@ +result +letsencrypt +index.html diff --git a/users/grfn/gws.fyi/Makefile b/users/grfn/gws.fyi/Makefile new file mode 100644 index 000000000000..d6c9f40c9536 --- /dev/null +++ b/users/grfn/gws.fyi/Makefile @@ -0,0 +1,31 @@ +.PHONY: deploy + +deploy: + @$(shell nix-build `git rev-parse --show-toplevel` -A 'users.grfn."gws.fyi"') + +renew: + @echo Renewing... + @certbot certonly \ + --manual \ + --domain www.gws.fyi \ + --preferred-challenges dns \ + --server https://acme-v02.api.letsencrypt.org/directory \ + --agree-tos \ + --work-dir $(shell pwd)/letsencrypt/work \ + --logs-dir $(shell pwd)/letsencrypt/logs \ + --config-dir $(shell pwd)/letsencrypt/config + @echo "Reimporting certificate" + @aws acm import-certificate \ + --profile personal \ + --certificate file://letsencrypt/config/live/www.gws.fyi/cert.pem \ + --certificate-chain file://letsencrypt/config/live/www.gws.fyi/fullchain.pem \ + --private-key file://letsencrypt/config/live/www.gws.fyi/privkey.pem \ + --certificate-arn arn:aws:acm:us-east-1:797089351721:certificate/628e54f3-55f9-49c0-811a-eba516b68e30 \ + --region us-east-1 + +backup: + @tarsnap -cf $(shell uname -n)-letsencrypt-$(shell date +%Y-%m-%d_%H-%M-%S) \ + letsencrypt/ + +open: + $$BROWSER "https://www.gws.fyi" diff --git a/users/grfn/gws.fyi/config.el b/users/grfn/gws.fyi/config.el new file mode 100644 index 000000000000..b05d897d3ddb --- /dev/null +++ b/users/grfn/gws.fyi/config.el @@ -0,0 +1,6 @@ +(require 'org) + +(setq org-html-postamble nil) + +(defadvice org-export-grab-title-from-buffer + (around org-export-grab-title-from-buffer-disable activate)) diff --git a/users/grfn/gws.fyi/default.nix b/users/grfn/gws.fyi/default.nix new file mode 100644 index 000000000000..5b7d8fc0e19a --- /dev/null +++ b/users/grfn/gws.fyi/default.nix @@ -0,0 +1,35 @@ +args@{ pkgs, depot, ... }: +with pkgs; +let + site = import ./site.nix args; + resume = import ../resume args; + bucket = "s3://gws.fyi"; + distributionID = "E2ST43JNBH8C64"; + + css = runCommand "main.css" { + buildInputs = [ pkgs.minify ]; + } '' + minify --type css < ${./main.css} > $out + ''; + + keys = runCommand "ssh-keys" {} '' + touch $out + echo "${depot.users.grfn.keys.main}" >> $out + ''; + + website = + runCommand "gws.fyi" { } '' + mkdir -p $out + cp ${css} $out/main.css + cp ${site.index} $out/index.html + cp -r ${site.recipes} $out/recipes + cp ${resume} $out/resume.pdf + cp ${keys} $out/keys + ''; + +in (writeShellScript "deploy.sh" '' + ${awscli2}/bin/aws --profile personal s3 sync ${website}/ ${bucket} + echo "Deployed to http://gws.fyi" +'') // { + inherit website site; +} diff --git a/users/grfn/gws.fyi/index.org b/users/grfn/gws.fyi/index.org new file mode 100644 index 000000000000..fa47b7c03668 --- /dev/null +++ b/users/grfn/gws.fyi/index.org @@ -0,0 +1,39 @@ +#+OPTIONS: title:nil toc:nil num:nil +#+HTML_HEAD: <title>griffin smith</title> +#+HTML_HEAD: <link rel="stylesheet" href="./main.css"> + +my name is griffin ward smith (aka grfn, glittershark, gws) and i'm a software +engineer and musician + +* code + +- [[https://github.com/glittershark/][github]] +- [[https://cs.tvl.fyi/depot/-/tree/users/grfn][my directory in the tvl monorepo]] + +* work + +i'm currently working for a relatively new stealth-mode startup. i can't talk much +about it yet, but if you're interested in rust, distributed systems, and +databases please reach out + +* projects + +- [[https://windtunnel.ci/][windtunnel]], a continuous benchmarking software-as-a-service currently accepting early alpha users (send me an email if you want to try it out!) +- [[https://cs.tvl.fyi/depot/-/tree/users/grfn/achilles][achilles]], a compiler for (what I plan to become) a dependently typed, low-level functional programming language targeting LLVM +- [[https://github.com/glittershark/org-clubhouse][org-clubhouse]], an emacs package for lightweight integration between [[https://orgmode.org/][org-mode]] and [[https://clubhouse.io/][the clubhouse project management tool]] +- [[https://cs.tvl.fyi/depot/-/tree/users/grfn/xanthous][xanthous]], a terminal roguelike in haskell that I work on intermittently and exclusively for fun \\ + you can now try xanthous out over ssh by running ~ssh -p 2222 xanthous.gws.fyi~. if you do so I'd love if you send me an email about it + +* music + +- https://sacrosanct.bandcamp.com/, a post-rock project with a [[https://bandcamp.com/h34rken][friend of mine]] +- [[https://soundcloud.com/missingggg][my current soundcloud]], releasing instrumental hip-hop under the name *missing* +- you can also find a log of all the music I listen to [[https://www.last.fm/user/wildgriffin45][on last.fm]] + +* contact + +- [[mailto:web@gws.fyi][web@gws.fyi]] +- [[https://twitter.com/glittershark1][twitter]] +- https://keybase.io/glittershark +- grfn on IRC (hackint or libera.chat) +- [[http://keys.gnupg.net/pks/lookup?op=get&search=0x44EF5B5E861C09A7][gpg key: 0F11A989879E8BBBFDC1E23644EF5B5E861C09A7]] diff --git a/users/grfn/gws.fyi/main.css b/users/grfn/gws.fyi/main.css new file mode 100644 index 000000000000..cdcd440766ca --- /dev/null +++ b/users/grfn/gws.fyi/main.css @@ -0,0 +1,139 @@ +@import url(https://fonts.googleapis.com/css?family=Inconsolata|Inter&display=swap); + +body { + margin-top: 40px; + max-width: 900px; + line-height: 1.6; + font-size: 16px; + background: #f8f3ff; + color: #3a1616; + padding: 0 10px; + font-family: Inter, sans-serif; +} + +@media (min-width: 1050px) { + body { + margin-left: 150px; + } +} + +@media (min-width: 2000px) { + body { + margin-left: 300px; + } +} + +input { + padding: 10px 16px; + margin: 2px 0; + box-sizing: border-box; + border: 2px solid #dabebe; + border-radius: 6px; + background: #f8f3ff; + color: #3a1616; + font-size: 16px; + -webkit-transition: 0.5s; + transition: 0.5s; + outline: 0; +} + +input:focus { + border: 2px solid #3a1616; +} + +.button { + background-color: #f8f3ff; + border: none; + color: #000; + padding: 6px 14px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + transition-duration: 0.4s; + cursor: pointer; + border: 2px solid #3a1616; + border-radius: 6px; +} + +.button:hover { + background-color: #3a1616; + color: #fff; +} + +.isa_error, +.isa_info, +.isa_success, +.isa_warning { + width: 90%; + margin: 10px 0; + padding: 12px; +} + +.isa_info { + color: #00529b; + background-color: #bde5f8; +} + +.isa_success { + color: #4f8a10; + background-color: #dff2bf; +} + +.isa_warning { + color: #9f6000; + background-color: #feefb3; +} + +.isa_error { + color: #d8000c; + background-color: #ffd2d2; +} + +h1, +h2, +h3 { + line-height: 1.2; + font-family: Inter, sans-serif; +} + +h1.title, +h2.title, +h3.title { + text-align: left; + margin-bottom: 1.5em; +} + +h2 { + font-size: 18px; +} + +img { + max-width: 750px; + border-radius: 10px; +} + +a { + cursor: pointer; + color: #217ab7; + line-height: inherit; +} + +a:hover { + background-color: #e3d6ff; +} + +a:visited { + color: #43458b; + border-color: #43458b; +} + +pre { + font-family: Inconsolata, monospace; +} + +::selection { + color: #fff; + background: #ff4081; +} diff --git a/users/grfn/gws.fyi/orgExportHTML.nix b/users/grfn/gws.fyi/orgExportHTML.nix new file mode 100644 index 000000000000..ac28580a5926 --- /dev/null +++ b/users/grfn/gws.fyi/orgExportHTML.nix @@ -0,0 +1,70 @@ +{ pkgs, depot, ... }: + +with pkgs; +with lib; + +let + + emacsWithPackages = (pkgs.emacsPackagesGen pkgs.emacs27).emacsWithPackages; + + emacs = emacsWithPackages (p: with p; [ + org + ]); + +in + +opts: + +let + src = if isAttrs opts then opts.src else opts; + headline = if isAttrs opts then opts.headline else null; + + bn = builtins.baseNameOf src; + filename = elemAt (splitString "." bn) 0; + + outName = + if isNull headline + then + let bn = builtins.baseNameOf src; + filename = elemAt (splitString "." bn) 0; + in + if depot.nix.utils.isDirectory src + then filename + else filename + ".html" + else "${filename}-${replaceStrings [" "] ["-"] filename}.html"; + + escapeDoubleQuotes = replaceStrings ["\""] ["\\\""]; + + navToHeadline = optionalString (! isNull headline) '' + (search-forward "${escapeDoubleQuotes headline}") + (org-narrow-to-subtree) + ''; + +in + +runCommand outName { inherit src; } '' + buildFile() { + cp "$1" file.org + ${emacs}/bin/emacs --batch \ + --load ${./config.el} \ + --visit file.org \ + --eval "(progn + ${escapeDoubleQuotes navToHeadline} + (org-html-export-to-html))" \ + --kill + rm file.org + substitute file.html "$2" \ + --replace '<title>‎</title>' "" + rm file.html + } + + if [ -d $src ]; then + for file in $src/*; do + result=''${file/$src/$out} + mkdir -p $(dirname $result) + buildFile $file ''${result/.org/.html} + done + else + buildFile $src $out + fi +'' diff --git a/users/grfn/gws.fyi/recipes/tomato-sauce.org b/users/grfn/gws.fyi/recipes/tomato-sauce.org new file mode 100644 index 000000000000..74e9b103c670 --- /dev/null +++ b/users/grfn/gws.fyi/recipes/tomato-sauce.org @@ -0,0 +1,102 @@ +#+TITLE: Tomato Sauce +#+OPTIONS: toc:nil num:nil +#+HTML_HEAD: <link rel="stylesheet" href="../main.css"> + +This is a general, all-purpose framework for turning some form of tomatoes into +some form of sauce. You can use fresh tomatoes or canned (the latter are really +quite surprisingly good sometimes), and include or omit garlic, basil, or other +add-ins. The only real non-negotiable ingredients are tomatoes (duh), onion, and +some kind of fat (I prefer butter). + +* Sauce + +1. *Prep*. If starting with canned tomatoes, skip this step. if starting with + whole tomatoes (which you should really only ever do if you grew them + yourself or got them fresh at a farmers market, grocery store tomatoes are + kinda sad), first, peel the tomatoes. The easiest way to do this is to score + them with an X pattern cut as shallow as possible while still breaking the + skin, trying to cover the whole surface area of the tomato, blanch them + briefly in boiling water, then dunk into an ice bath. After this, the skins + will slip right off. After peeling, cut out the stem, core, and any green or + brown bits, and go to the next step + +2. *Base layer*. Couple of variables here, though a perfectly good (in fact, my + usual go-to) tomato sauce can also skip this entire step: + - If you want meat with your sauce (pancetta/guanciale/bacon for an + amatriciana, ground beef or pork for a bolognese) you'll start out by + sautรฉing that in some sort of fat (probably olive oil), less fat for meat + with a lot of fat already in it, to brown and render out fat from the meat + - If you want onion in the final sauce, you'll chop them finely and sautรฉ + them with whatever fat you've got (either from the meat, or olive oil or + butter if you're not making a meat sauce). Remember to always add a *bit* + of salt when sautรฉing onion like this, not for flavor but to draw out the + moisture. If you just want onion flavor but not bits of onion in the final + sauce, it's added whole later (so ignore this bullet point). + - If you feel like it (sometimes I do, usually I don't) you can also mince + garlic here and sautรฉ that in with everything else. Add a little after the + onion, as garlic cooks slower than onion unless you want something roastier + (usually you don't for tomato sauce) + - The traditional (so I'm told) thing to do with amatriciana, but also nice + with all variations, is to add in a little crushed red pepper with the + fat to flavor it slightly, but do this late so it doesn't burn + - If you have tomato paste on hand and feel like using it, it's also nice to + fry that in the oil for a little bit - usually I'd do that around the same + time as the garlic + + If you're making tomato *paste* from your sauce, skip all of this - paste is + an ingredient, not a sauce on its own, so imo should be as neutral as + possible (i.e. just tomato). + +3. *Tomato layer*. Not a whole lot to do here, just add all of your tomatoes - + either your peeled and de-cored tomatoes from step 1 if you're using whole + tomatoes, or an entire can of whole, peeled san marzano tomatoes, including + the juice in the can - to a pot over medium-high heat. If you need more fat + or if you skipped step 2, this is where you'd add it - a classic and my + personal favorite is like 2/3rds to 3/4ths of a stick of butter, but you can + also go with olive oil. If you skipped the onion in step 2, add that here + too - usually that'd just be a fist-sized amount of onion or so peeled but + left with the stem on so you can fish it out from your final sauce later (and + snack on it!). Also salt here, again not to taste but primarily to draw out + moisture from the various ingredients. + +4. You can cook that for a wide variety of times, especially depending on how + hot you make your stove - there ends up being *lot* of liquid in there, so + you can go (in my experience) a reasonable amount hotter than you expect + without burning the sauce, though obviously your mileage may vary. The main + thing you're looking for is the whole chunks of tomato to break down, and the + whole sauce to get a texture that looks like it'll end up sticking to pasta + nicely. In all versions of this, stir pretty regularly with a wooden spoon, + and use the spoon to crush the big chunks of tomato occasionally. + +5. *Final layer*. Usually I don't do anything here - but if you feel + like it, usually right as you take stuff off the heat is where you'd add + basil, if you're using it. You can also add sugar to balance out too much + acidity from an especially acidic tomato here - I'm not going to tell anyone. + Also salt, but make sure to account for the extra salt you're gonna get from + the pasta water (see step 6) + +6. *Pasta*. You know how to cook pasta, I'm not going to tell you that. But, + like, salt your water until it tastes too salty, and remember to move the + pasta itself *directly* into the sauce pot from the pasta pot before it's + completely done cooking and without straining, bringing along some of the + pasta water (and a little extra for good measure) then finishing the pasta in + the sauce. You know, the thing you do for pasta. Remember the pasta water + will have salt in it, so adjust for that when salting the sauce overall (I + have made this mistake and ended up with too-salty pasta sauce). + +* Paste + +Start with the above recipe for tomato sauce, noting especially that (in my +opinion) you should skip step 2 entirely. Keep cooking the sauce until it's +*too* thick for pasta sauce (but don't burn it!), then spread it out across some +sort of lined sheet pan (like a silpat, if you've got one) and bake in the oven +at like 250-300 degrees for a *hell* of a long time - I've seen this take like +10 hours, for an especially juicy batch of tomatoes, but obviously keep a close +eye on it because it *definitely will burn* eventually. You're looking for the +end result to be the texture of tomato paste, because that's what the recipe is +for. Especially if you're using garden-grown or otherwise fresh tomatoes, +you'll notice quite a few seeds in the final product - don't worry too much +about those, they've never bothered me. Once everything's done and cooled down, +store in a jar in a fridge, topped with olive oil to seal things off and prevent +oxidation. Use in all your future endeavors, including the tomato sauce recipe +above itself. Tomato sauce is a beautiful oroborous. diff --git a/users/grfn/gws.fyi/shell.nix b/users/grfn/gws.fyi/shell.nix new file mode 100644 index 000000000000..846bdb6677a3 --- /dev/null +++ b/users/grfn/gws.fyi/shell.nix @@ -0,0 +1,9 @@ +with import <nixpkgs> { config.allowUnfree = true; }; +mkShell { + buildInputs = [ + awscli + gnumake + letsencrypt + tarsnap + ]; +} diff --git a/users/grfn/gws.fyi/site.nix b/users/grfn/gws.fyi/site.nix new file mode 100644 index 000000000000..33d4a71e7b1f --- /dev/null +++ b/users/grfn/gws.fyi/site.nix @@ -0,0 +1,12 @@ +args@{ pkgs ? import <nixpkgs> {}, ... }: + +let + + orgExportHTML = import ./orgExportHTML.nix args; + +in + +{ + index = orgExportHTML ./index.org; + recipes = orgExportHTML ./recipes; +} diff --git a/users/grfn/keyboard/.gitignore b/users/grfn/keyboard/.gitignore new file mode 100644 index 000000000000..b2be92b7db01 --- /dev/null +++ b/users/grfn/keyboard/.gitignore @@ -0,0 +1 @@ +result diff --git a/users/grfn/keyboard/README.org b/users/grfn/keyboard/README.org new file mode 100644 index 000000000000..b085883a1049 --- /dev/null +++ b/users/grfn/keyboard/README.org @@ -0,0 +1,10 @@ +This repository contains the source of the keyboard layout for my Ergodox EZ, +plus build tooling based on Nix. + +To flash to an Ergodox EZ that's connected to your computer via USB, run: + +#+BEGIN_SRC shell +./flash +#+END_SRC + +then press the reset switch on the keyboard. diff --git a/users/grfn/keyboard/default.nix b/users/grfn/keyboard/default.nix new file mode 100644 index 000000000000..dda1d9682cbf --- /dev/null +++ b/users/grfn/keyboard/default.nix @@ -0,0 +1,63 @@ +{ pkgs, ... }: + +with pkgs; + +let avrlibc = pkgsCross.avr.libcCross; in + +rec { + qmkSource = fetchgit { + url = "https://github.com/qmk/qmk_firmware"; + rev = "ab1650606c36f85018257aba65d9c3ff8ec42e71"; + sha256 = "1k59flkvhjzmfl0yz9z37lqhvad7m9r5wy1p1sjk5274rsmylh79"; + fetchSubmodules = true; + }; + + layout = stdenv.mkDerivation rec { + name = "ergodox_ez_grfn.hex"; + + src = qmkSource; + + buildInputs = [ + dfu-programmer + dfu-util + diffutils + git + python3 + pkgsCross.avr.buildPackages.binutils + pkgsCross.avr.buildPackages.gcc + avrlibc + avrdude + ]; + + AVR_CFLAGS = [ + "-isystem ${avrlibc}/avr/include" + "-L${avrlibc}/avr/lib/avr5" + ]; + + AVR_ASFLAGS = AVR_CFLAGS; + + patches = [ ./increase-tapping-delay.patch ]; + + postPatch = '' + mkdir keyboards/ergodox_ez/keymaps/grfn + cp ${./keymap.c} keyboards/ergodox_ez/keymaps/grfn/keymap.c + ''; + + buildPhase = '' + make ergodox_ez:grfn + ''; + + installPhase = '' + cp ergodox_ez_grfn.hex $out + ''; + }; + + flash = writeShellScript "flash.sh" '' + ${teensy-loader-cli}/bin/teensy-loader-cli \ + -v \ + --mcu=atmega32u4 \ + -w ${layout} + ''; + + meta.targets = [ "layout" ]; +} diff --git a/users/grfn/keyboard/flash b/users/grfn/keyboard/flash new file mode 100755 index 000000000000..76def36f9ca8 --- /dev/null +++ b/users/grfn/keyboard/flash @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec "$(nix-build --no-out-link ../../.. -A users.grfn.keyboard.flash)" diff --git a/users/grfn/keyboard/increase-tapping-delay.patch b/users/grfn/keyboard/increase-tapping-delay.patch new file mode 100644 index 000000000000..316c435fed6c --- /dev/null +++ b/users/grfn/keyboard/increase-tapping-delay.patch @@ -0,0 +1,13 @@ +diff --git a/keyboards/ergodox_ez/config.h b/keyboards/ergodox_ez/config.h +index ae70c4f2e..776110c09 100644 +--- a/keyboards/ergodox_ez/config.h ++++ b/keyboards/ergodox_ez/config.h +@@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. + /* define if matrix has ghost */ + //#define MATRIX_HAS_GHOST + +-#define TAPPING_TERM 200 ++#define TAPPING_TERM 150 + #define IGNORE_MOD_TAP_INTERRUPT // this makes it possible to do rolling combos (zx) with keys that convert to other keys on hold (z becomes ctrl when you hold it, and when this option isn't enabled, z rapidly followed by x actually sends Ctrl-x. That's bad.) + + /* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ diff --git a/users/grfn/keyboard/keymap.c b/users/grfn/keyboard/keymap.c new file mode 100644 index 000000000000..fbb28c9aac59 --- /dev/null +++ b/users/grfn/keyboard/keymap.c @@ -0,0 +1,206 @@ +#include QMK_KEYBOARD_H +#include "debug.h" +#include "action_layer.h" +#include "version.h" + + +#include "keymap_german.h" + +#include "keymap_nordic.h" + + + +enum custom_keycodes { + PLACEHOLDER = SAFE_RANGE, // can always be here + EPRM, + VRSN, + RGB_SLD, + + EX_PIPE, // |> + THIN_ARROW, // -> + FAT_ARROW, // => +}; + + + +#define LAMBDA UC(0x03BB) + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + + [0] = LAYOUT_ergodox( + KC_EQUAL, KC_1, KC_2, KC_3, KC_4, KC_5, KC_LEFT, + KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_LALT, + KC_ESCAPE, KC_A, KC_S, KC_D, KC_F, KC_G, + KC_RSFT, CTL_T(KC_Z), KC_X, KC_C, KC_V, KC_B, KC_TAB, + LT(1,KC_GRAVE), KC_QUOTE, LALT(KC_LSHIFT),KC_LEFT,KC_RIGHT, + ALT_T(KC_APPLICATION), LAMBDA, + KC_LBRACKET, + GUI_T(KC_NO), LSFT_T(KC_BSPACE), KC_COLN, + + KC_MY_COMPUTER, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINUS, + KC_RALT, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSLASH, + KC_H, KC_J, KC_K, KC_L, LT(2,KC_SCOLON), LT(1,KC_QUOTE), + KC_MINUS, KC_N, KC_M, KC_COMMA, KC_DOT, CTL_T(KC_SLASH), KC_RSFT, + KC_DOWN,KC_UP, KC_LBRACKET,KC_RBRACKET,MO(1), + + KC_PAUSE, TG(3), + KC_RBRACKET, + KC_COLN, RSFT_T(KC_ENTER), KC_SPACE + ), + + [1] = LAYOUT_ergodox( + KC_ESCAPE, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_TRANSPARENT, + KC_TRANSPARENT, KC_EXLM, KC_AT, KC_LCBR, KC_RCBR, KC_PIPE, KC_RABK, + KC_TRANSPARENT, KC_HASH, KC_DLR, KC_LPRN, KC_RPRN, KC_UNDERSCORE, + KC_LABK, KC_PERC, KC_CIRC, KC_LBRACKET, KC_RBRACKET, KC_TILD, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + RGB_MOD, KC_TRANSPARENT, + KC_TRANSPARENT, + RGB_VAD, RGB_VAI, EX_PIPE, + + KC_TRANSPARENT, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, + KC_PGUP, KC_UP, KC_7, KC_8, KC_9, KC_ASTR, KC_F12, + KC_DOWN, KC_4, KC_5, KC_6, KC_PLUS, KC_TRANSPARENT, + KC_PGDOWN, KC_AMPR, KC_1, KC_2, KC_3, KC_BSLASH, KC_TRANSPARENT, + KC_TRANSPARENT, KC_DOT, KC_0, KC_EQUAL, KC_TRANSPARENT, + RGB_TOG, RGB_SLD, + THIN_ARROW, + EX_PIPE, RGB_HUD, RGB_HUI + ), + + [2] = LAYOUT_ergodox( + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_MS_UP, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_MS_LEFT, KC_MS_DOWN, KC_MS_RIGHT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_MS_BTN1, KC_MS_BTN2, + KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, + KC_MS_BTN1, KC_MS_BTN2, KC_TRANSPARENT, + + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_MS_WH_DOWN, KC_MS_WH_UP, KC_TRANSPARENT, KC_TRANSPARENT, KC_MEDIA_PLAY_PAUSE, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_MEDIA_PREV_TRACK, KC_MEDIA_NEXT_TRACK, KC_TRANSPARENT, KC_TRANSPARENT, + KC_AUDIO_VOL_DOWN, KC_AUDIO_VOL_UP, KC_AUDIO_MUTE, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_WWW_BACK), + + // FPS layout + [3] = LAYOUT_ergodox( + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, + KC_SPACE, KC_TRANSPARENT, KC_TRANSPARENT, + + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, + KC_TRANSPARENT, TG(3), + KC_TRANSPARENT, + KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT), +}; + +const uint16_t PROGMEM fn_actions[] = { + [1] = ACTION_LAYER_TAP_TOGGLE(1) +}; + +// leaving this in place for compatibilty with old keymaps cloned and re-compiled. +const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) +{ + switch(id) { + case 0: + if (record->event.pressed) { + SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION); + } + break; + } + return MACRO_NONE; +}; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + // dynamically generate these. + case EPRM: + if (record->event.pressed) { + eeconfig_init(); + } + return false; + break; + case VRSN: + if (record->event.pressed) { + SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION); + } + return false; + break; + case RGB_SLD: + if (record->event.pressed) { + rgblight_mode(1); + } + return false; + break; + case EX_PIPE: + if (record->event.pressed) { + SEND_STRING ( "|> " ); + } + return false; + break; + case THIN_ARROW: + if (record->event.pressed) { + SEND_STRING ( "-> " ); + } + return false; + break; + + + } + return true; +} + +void matrix_scan_user(void) { + + uint8_t layer = biton32(layer_state); + + ergodox_board_led_off(); + ergodox_right_led_1_off(); + ergodox_right_led_2_off(); + ergodox_right_led_3_off(); + switch (layer) { + case 1: + ergodox_right_led_1_on(); + break; + case 2: + ergodox_right_led_2_on(); + break; + case 3: + ergodox_right_led_3_on(); + break; + case 4: + ergodox_right_led_1_on(); + ergodox_right_led_2_on(); + break; + case 5: + ergodox_right_led_1_on(); + ergodox_right_led_3_on(); + break; + case 6: + ergodox_right_led_2_on(); + ergodox_right_led_3_on(); + break; + case 7: + ergodox_right_led_1_on(); + ergodox_right_led_2_on(); + ergodox_right_led_3_on(); + break; + default: + break; + } + +}; diff --git a/users/grfn/keys.nix b/users/grfn/keys.nix new file mode 100644 index 000000000000..c52229b3a898 --- /dev/null +++ b/users/grfn/keys.nix @@ -0,0 +1,5 @@ +{ ... }: +{ + whitby = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDIwl+xQYRCk6Ijz/Ll8eXKZrcTH9/7xwlvIowiuqDSFtGkf+73QJkwVJ0YiKHWAPwIUWMzCEO/Ab2g6j4PcR+XYu8kXbrwT5aW65L/AK1oaav2RfV1bnQEVUP9FRPL52BN42J0ibI2QJZKJVws9JF7vxTWPPG0V0eoxcaRMk1ZEqq+/k3GuN8D69VSV8xo9lB8yZEvTxs0YQRiiF7Q6t/3jhYtz6lCdazQviRcSEOj5AVsDjcf1XIAPOcLK4Q4OEXL49T3UaitSYMyKIO8hzNLiyGAUlSbshAnutPXdyNBypkCs6FrSPSRdBfFjzUVE/a+JWCPmx0q0xAVd497Efxby+Vsa2/TPMp7tSisPaqk3MpPmjBS7eI/y4Pl2GpAB4OVANEBNd1Q6K2/37Pk+PrZtIUBiRG8sM0Od36BjwLCxvG0G5P/UYZ93aC8GzqkRf4evOBMiJCvR2o9CDEDycNyTm1y5dyJzQewOTWX9nsiF1rllc92W0ZALvpO03+W2+k= grfn@chupacabra"; + main = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHPiNpPB6Uqs/VSW/C8tR/Z5wCQxKppNL2iETb1ucsYsFf1B2apG5txj06NMT6IWXwWpZXq7ld+/sA+a2I03lO2INP7S1Dto5nAwpNhhKN/UBXk76qYTdY5tEvb9J89S2ZzfQWR30aZ0CEDDrcbc+YktU1eSLdluu6QH+M/uPBweSiVn5wNHkc5sRdbyiVsZSQJ41MO7PQrzGpe7Pxola/ghOHdEFlESJMKA5uoRpCGboxtDE9tMJwG5MxNwHERpfI9FjvvLsJRrp9dRf6A/RQjlV/nb1GmpX0I8pvrXEPxm/l0rOAgE81VSsM+BxJ7ZvCe8u/YqMYJ8xVfskzlVsf griffin@MacBook-Pro"; +} diff --git a/users/grfn/org-clubhouse/.gitignore b/users/grfn/org-clubhouse/.gitignore new file mode 100644 index 000000000000..2a7dd97debf1 --- /dev/null +++ b/users/grfn/org-clubhouse/.gitignore @@ -0,0 +1,3 @@ +# Spacemacs +org-clubhouse-autoloads.el +org-clubhouse-pkg.el diff --git a/users/grfn/org-clubhouse/CODE_OF_CONDUCT.org b/users/grfn/org-clubhouse/CODE_OF_CONDUCT.org new file mode 100644 index 000000000000..f15e387d5464 --- /dev/null +++ b/users/grfn/org-clubhouse/CODE_OF_CONDUCT.org @@ -0,0 +1,101 @@ +* Contributor Covenant Code of Conduct + :PROPERTIES: + :CUSTOM_ID: contributor-covenant-code-of-conduct + :END: + +** Our Pledge + :PROPERTIES: + :CUSTOM_ID: our-pledge + :END: + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our +project and our community a harassment-free experience for everyone, +regardless of age, body size, disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +** Our Standards + :PROPERTIES: + :CUSTOM_ID: our-standards + :END: + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual + attention or advances +- Trolling, insulting/derogatory comments, and personal or political + attacks +- Public or private harassment +- Publishing others' private information, such as a physical or + electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +** Our Responsibilities + :PROPERTIES: + :CUSTOM_ID: our-responsibilities + :END: + +Project maintainers are responsible for clarifying the standards of +acceptable behavior and are expected to take appropriate and fair +corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, +or reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, or to ban +temporarily or permanently any contributor for other behaviors that they +deem inappropriate, threatening, offensive, or harmful. + +** Scope + :PROPERTIES: + :CUSTOM_ID: scope + :END: + +This Code of Conduct applies within all project spaces, and it also +applies when an individual is representing the project or its community +in public spaces. Examples of representing a project or community +include using an official project e-mail address, posting via an +official social media account, or acting as an appointed representative +at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +** Enforcement + :PROPERTIES: + :CUSTOM_ID: enforcement + :END: + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported by contacting the project team at root@gws.fyi. All +complaints will be reviewed and investigated and will result in a +response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to +the reporter of an incident. Further details of specific enforcement +policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in +good faith may face temporary or permanent repercussions as determined +by other members of the project's leadership. + +** Attribution + :PROPERTIES: + :CUSTOM_ID: attribution + :END: + +This Code of Conduct is adapted from the +[[https://www.contributor-covenant.org][Contributor Covenant]], version +1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/users/grfn/org-clubhouse/LICENSE b/users/grfn/org-clubhouse/LICENSE new file mode 100644 index 000000000000..1777f0fac3ea --- /dev/null +++ b/users/grfn/org-clubhouse/LICENSE @@ -0,0 +1,7 @@ +Copyright (C) 2018 Off Market Data, Inc. DBA Urbint + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/users/grfn/org-clubhouse/README.org b/users/grfn/org-clubhouse/README.org new file mode 100644 index 000000000000..9cd8fbe8921d --- /dev/null +++ b/users/grfn/org-clubhouse/README.org @@ -0,0 +1,142 @@ +#+TITLE:Org-Clubhouse + +Simple, unopinionated integration between Emacs's [[https://orgmode.org/][org-mode]] and the [[https://clubhouse.io/][Clubhouse]] +issue tracker + +(This used to be at urbint/org-clubhouse, by the way, but moved here as it's +more of a personal project than a company one) + +* Installation + +** [[https://github.com/quelpa/quelpa][Quelpa]] + +#+BEGIN_SRC emacs-lisp +(quelpa '(org-clubhouse + :fetcher github + :repo "glittershark/org-clubhouse")) +#+END_SRC + +** [[https://github.com/hlissner/doom-emacs/][DOOM Emacs]] + +#+BEGIN_SRC emacs-lisp +;; in packages.el +(package! org-clubhouse + :recipe (:fetcher github + :repo "glittershark/org-clubhouse" + :files ("*"))) + +;; in config.el +(def-package! org-clubhouse) +#+END_SRC + +** [[http://spacemacs.org/][Spacemacs]] +#+BEGIN_SRC emacs-lisp +;; in .spacemacs (SPC+fed) + dotspacemacs-additional-packages + '((org-clubhouse :location (recipe :fetcher github :repo "glittershark/org-clubhouse"))) +#+END_SRC + + +* Setup + +Once installed, you'll need to set three global config vars: + +#+BEGIN_SRC emacs-lisp +(setq org-clubhouse-auth-token "<your-token>" + org-clubhouse-team-name "<your-team-name>" + org-clubhouse-username "<your-username>") +#+END_SRC + +You can generate a new personal API token by going to the "API Tokens" tab on +the "Settings" page in the clubhouse UI. + +Note that ~org-clubhouse-username~ needs to be set to your *mention name*, not +your username, as currently there's no way to get the ID of a user given their +username in the clubhouse API + +* Usage + +** Reading from clubhouse + +- ~org-clubhouse-headlines-from-query~ + Create org-mode headlines from a [[https://help.clubhouse.io/hc/en-us/articles/360000046646-Searching-in-Clubhouse-Story-Search][clubhouse query]] at the cursor's current + position, prompting for the headline indentation level and clubhouse query + text +- ~org-clubhouse-headline-from-story~ + Prompts for headline indentation level and the title of a story (which will + complete using the titles of all stories in your Clubhouse workspace) and + creates an org-mode headline from that story +- ~org-clubhouse-headline-from-story-id~ + Creates an org-mode headline directly from the ID of a clubhouse story + +** Writing to clubhouse + +- ~org-clubhouse-create-story~ + Creates a new Clubhouse story from the current headline, or if a region of + headlines is selected bulk-creates stories with all those headlines +- ~org-clubhouse-create-epic~ + Creates a new Clubhouse epic from the current headline, or if a region of + headlines is selected bulk-creates epics with all those headlines +- ~org-clubhouse-create-story-with-task-list~ + Creates a Clubhouse story from the current headline, making all direct + children of the headline into tasks in the task list of the story +- ~org-clubhouse-push-task-list~ + Writes each child element of the current clubhouse element as a task list + item of the associated clubhouse ID. +- ~org-clubhouse-update-story-title~ + Updates the title of the Clubhouse story linked to the current headline with + the text of the headline +- ~org-clubhouse-update-description~ + Update the status of the Clubhouse story linked to the current element with + the contents of a drawer inside the element called DESCRIPTION, if any exists +- ~org-clubhouse-claim~ + Adds the user configured in ~org-clubhouse-username~ as the owner of the + clubhouse story associated with the headline at point + +*** Automatically updating Clubhouse story statuses + +Org-clubhouse can be configured to update the status of stories as you update +their todo-keyword in org-mode. To opt-into this behavior, set the +~org-clubhouse-mode~ minor-mode: + +#+BEGIN_SRC emacs-lisp +(add-hook 'org-mode-hook #'org-clubhouse-mode nil nil) +#+END_SRC + +The mapping from org-mode todo-keywords is configured via the +~org-clubhouse-state-alist~ variable, which should be an [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html][alist]] mapping (string) +[[https://orgmode.org/manual/Workflow-states.html][org-mode todo-keywords]] to the (string) names of their corresponding workflow +state. You can have todo-keywords that don't map to a workflow state (I use this +in my workflow extensively) and org-clubhouse will just preserve the previous +state of the story when moving to that state. + +An example config: + +#+BEGIN_SRC emacs-lisp +(setq org-clubhouse-state-alist + '(("TODO" . "To Do") + ("ACTIVE" . "In Progress") + ("DONE" . "Done"))) +#+END_SRC + +* Philosophy + +I use org-mode every single day to manage tasks, notes, literate programming, +etc. Part of what that means for me is that I already have a system for the +structure of my .org files, and I don't want to sacrifice that system for any +external tool. Updating statuses, ~org-clubhouse-create-story~, and +~org-clubhouse-headline-from-story~ are my bread and butter for that reason - +rather than having some sort of bidirectional sync that pulls down full lists of +all the stories in Clubhouse (or whatever issue tracker / project management +tool I'm using at the time). I can be in a mode where I'm taking meeting notes, +think of something that I need to do, make it a TODO headline, and make that +TODO headline a clubhouse story. That's the same reason for the DESCRIPTION +drawers rather than just sending the entire contents of a headline to +Clubhouse - I almost always want to write things like personal notes, literate +code, etc inside of the tasks I'm working on, and don't always want to share +that with Clubhouse. + +* Configuration + +Refer to the beginning of the [[https://github.com/urbint/org-clubhouse/blob/master/org-clubhouse.el][org-clubhouse.el]] file in this repository for +documentation on all supported configuration variables diff --git a/users/grfn/org-clubhouse/org-clubhouse.el b/users/grfn/org-clubhouse/org-clubhouse.el new file mode 100644 index 000000000000..e6e29b575187 --- /dev/null +++ b/users/grfn/org-clubhouse/org-clubhouse.el @@ -0,0 +1,1241 @@ +;;; org-clubhouse.el --- Simple, unopinionated integration between org-mode and +;;; Clubhouse + +;;; Copyright (C) 2018 Off Market Data, Inc. DBA Urbint +;;; Permission is hereby granted, free of charge, to any person obtaining a copy +;;; of this software and associated documentation files (the "Software"), to +;;; deal in the Software without restriction, including without limitation the +;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +;;; sell copies of the Software, and to permit persons to whom the Software is +;;; furnished to do so, subject to the following conditions: + +;;; The above copyright notice and this permission notice shall be included in +;;; all copies or substantial portions of the Software. + +;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +;;; IN THE SOFTWARE. + +;;; Commentary: +;;; org-clubhouse provides simple, unopinionated integration between Emacs's +;;; org-mode and the Clubhouse issue tracker +;;; +;;; To configure org-clubhouse, create an authorization token in Cluhbouse's +;;; settings, then place the following configuration somewhere private: +;;; +;;; (setq org-clubhouse-auth-token "<auth_token>" +;;; org-clubhouse-team-name "<team-name>") +;;; + +;;; Code: + +(require 'cl-macs) +(require 'dash) +(require 'dash-functional) +(require 's) +(require 'org) +(require 'org-element) +(require 'subr-x) +(require 'ivy) +(require 'json) + +;;; +;;; Configuration +;;; + +(defvar org-clubhouse-auth-token nil + "Authorization token for the Clubhouse API.") + +(defvar org-clubhouse-username nil + "Username for the current Clubhouse user. + +Unfortunately, the Clubhouse API doesn't seem to provide this via the API given +an API token, so we need to configure this for +`org-clubhouse-claim-story-on-status-updates' to work") + +(defvar org-clubhouse-team-name nil + "Team name to use in links to Clubhouse. +ie https://app.clubhouse.io/<TEAM_NAME>/stories") + +(defvar org-clubhouse-project-ids nil + "Specific list of project IDs to synchronize with clubhouse. +If unset all projects will be synchronized") + +(defvar org-clubhouse-workflow-name "Default") + +(defvar org-clubhouse-default-story-type nil + "Sets the default story type. If set to 'nil', it will interactively prompt +the user each and every time a new story is created. If set to 'feature', +'bug', or 'chore', that value will be used as the default and the user will +not be prompted") + +(defvar org-clubhouse-state-alist + '(("LATER" . "Unscheduled") + ("[ ]" . "Ready for Development") + ("TODO" . "Ready for Development") + ("OPEN" . "Ready for Development") + ("ACTIVE" . "In Development") + ("PR" . "Review") + ("DONE" . "Merged") + ("[X]" . "Merged") + ("CLOSED" . "Merged")) + "Alist mapping org-mode todo keywords to their corresponding states in + Clubhouse. In `org-clubhouse-mode', moving headlines to these todo keywords + will update to the corresponding status in Clubhouse") + +(defvar org-clubhouse-story-types + '(("feature" . "Feature") + ("bug" . "Bug") + ("chore" . "Chore"))) + +(defvar org-clubhouse-default-story-types + '(("feature" . "Feature") + ("bug" . "Bug") + ("chore" . "Chore") + ("prompt" . "**Prompt each time (do not set a default story type)**"))) + +(defvar org-clubhouse-default-state "Proposed" + "Default state to create all new stories in.") + +(defvar org-clubhouse-claim-story-on-status-update 't + "Controls the assignee behavior of stories on status update. + +If set to 't, will mark the current user as the owner of any clubhouse +stories on any update to the status. + +If set to nil, will never automatically update the assignee of clubhouse +stories. + +If set to a list of todo-state's, will mark the current user as the owner of +clubhouse stories whenever updating the status to one of those todo states.") + +(defvar org-clubhouse-create-stories-with-labels nil + "Controls the way org-clubhouse creates stories with labels based on org tags. + +If set to 't, will create labels for all org tags on headlines when stories are +created. + +If set to 'existing, will set labels on created stories only if the label +already exists in clubhouse + +If set to nil, will never create stories with labels") + +;;; +;;; Utilities +;;; + +(defmacro comment (&rest _) + "Comment out one or more s-expressions." + nil) + +(defun ->list (vec) (append vec nil)) + +(defun reject-archived (item-list) + (-reject (lambda (item) (equal :json-true (alist-get 'archived item))) item-list)) + +(defun alist->plist (key-map alist) + (->> key-map + (-map (lambda (key-pair) + (let ((alist-key (car key-pair)) + (plist-key (cdr key-pair))) + (list plist-key (alist-get alist-key alist))))) + (-flatten-n 1))) + +(defun alist-get-equal (key alist) + "Like `alist-get', but uses `equal' instead of `eq' for comparing keys" + (->> alist + (-find (lambda (pair) (equal key (car pair)))) + (cdr))) + +(defun invert-alist (alist) + "Invert the keys and values of ALIST." + (-map (lambda (cell) (cons (cdr cell) (car cell))) alist)) + +(comment + + (alist->plist + '((foo . :foo) + (bar . :something)) + + '((foo . "foo") (bar . "bar") (ignored . "ignoreme!"))) + ;; => (:foo "foo" :something "bar") + + ) + +(defun find-match-in-alist (target alist) + (->> alist + (-find (lambda (key-value) + (string-equal (cdr key-value) target))) + car)) + +(defun org-clubhouse-collect-headlines (beg end) + "Collects the headline at point or the headlines in a region. Returns a list." + (if (and beg end) + (org-clubhouse-get-headlines-in-region beg end) + (list (org-element-find-headline)))) + + +(defun org-clubhouse-get-headlines-in-region (beg end) + "Collects the headlines from BEG to END" + (save-excursion + ;; This beg/end clean up pulled from `reverse-region`. + ;; it expands the region to include the full lines from the selected region. + + ;; put beg at the start of a line and end and the end of one -- + ;; the largest possible region which fits this criteria + (goto-char beg) + (or (bolp) (forward-line 1)) + (setq beg (point)) + (goto-char end) + ;; the test for bolp is for those times when end is on an empty line; + ;; it is probably not the case that the line should be included in the + ;; reversal; it isn't difficult to add it afterward. + (or (and (eolp) (not (bolp))) (progn (forward-line -1) (end-of-line))) + (setq end (point-marker)) + + ;; move to the beginning + (goto-char beg) + ;; walk by line until past end + (let ((headlines '()) + (before-end 't)) + (while before-end + (add-to-list 'headlines (org-element-find-headline)) + (let ((before (point))) + (org-forward-heading-same-level 1) + (setq before-end (and (not (eq before (point))) (< (point) end))))) + (reverse headlines)))) + +;;; +;;; Org-element interaction +;;; + +;; (defun org-element-find-headline () +;; (let ((current-elt (org-element-at-point))) +;; (if (equal 'headline (car current-elt)) +;; current-elt +;; (let* ((elt-attrs (cadr current-elt)) +;; (parent (plist-get elt-attrs :post-affiliated))) +;; (goto-char parent) +;; (org-element-find-headline))))) + +(defun org-element-find-headline () + (save-mark-and-excursion + (when (not (outline-on-heading-p)) (org-back-to-heading)) + (let ((current-elt (org-element-at-point))) + (when (equal 'headline (car current-elt)) + (cadr current-elt))))) + +(defun org-element-extract-clubhouse-id (elt &optional property) + (when-let* ((clubhouse-id-link (plist-get elt (or property :CLUBHOUSE-ID)))) + (cond + ((string-match + (rx "[[" (one-or-more anything) "]" + "[" (group (one-or-more digit)) "]]") + clubhouse-id-link) + (string-to-number (match-string 1 clubhouse-id-link))) + ((string-match-p + (rx buffer-start + (one-or-more digit) + buffer-end) + clubhouse-id-link) + (string-to-number clubhouse-id-link))))) + +(comment + (let ((strn "[[https://app.clubhouse.io/example/story/2330][2330]]")) + (string-match + (rx "[[" (one-or-more anything) "]" + "[" (group (one-or-more digit)) "]]") + strn) + (string-to-number (match-string 1 strn))) + ) + +(defun org-element-clubhouse-id () + (org-element-extract-clubhouse-id + (org-element-find-headline))) + +(defun org-clubhouse-clocked-in-story-id () + "Return the clubhouse story-id of the currently clocked-in org entry, if any." + (save-mark-and-excursion + (save-current-buffer + (when (org-clocking-p) + (set-buffer (marker-buffer org-clock-marker)) + (save-restriction + (when (or (< org-clock-marker (point-min)) + (> org-clock-marker (point-max))) + (widen)) + (goto-char org-clock-marker) + (org-element-clubhouse-id)))))) + +(comment + (org-clubhouse-clocked-in-story-id) + ) + +(defun org-element-and-children-at-point () + (let* ((elt (org-element-find-headline)) + (contents-begin (or (plist-get elt :contents-begin) + (plist-get elt :begin))) + (end (plist-get elt :end)) + (level (plist-get elt :level)) + (children '())) + (save-excursion + (goto-char (+ contents-begin (length (plist-get elt :title)))) + (while (< (point) end) + (let* ((next-elt (org-element-at-point)) + (elt-type (car next-elt)) + (elt (cadr next-elt))) + (when (and (eql 'headline elt-type) + (eql (+ 1 level) (plist-get elt :level))) + (push elt children)) + (goto-char (plist-get elt :end))))) + (append elt `(:children ,(reverse children))))) + +(defun +org-element-contents (elt) + (if-let ((begin (plist-get (cadr elt) :contents-begin)) + (end (plist-get (cadr elt) :contents-end))) + (buffer-substring-no-properties begin end) + "")) + +(defun org-clubhouse-find-description-drawer () + "Try to find a DESCRIPTION drawer in the current element." + (let ((elt (org-element-at-point))) + (cl-case (car elt) + ('drawer (+org-element-contents elt)) + ('headline + (when-let ((drawer-pos (string-match + ":DESCRIPTION:" + (+org-element-contents elt)))) + (save-excursion + (goto-char (+ (plist-get (cadr elt) :contents-begin) + drawer-pos)) + (org-clubhouse-find-description-drawer))))))) + +(defun org-clubhouse--labels-for-elt (elt) + "Return the Clubhouse labels based on the tags of ELT and the user's config." + (unless (eq nil org-clubhouse-create-stories-with-labels) + (let ((tags (org-get-tags (plist-get elt :contents-begin)))) + (-map (lambda (l) `((name . ,l))) + (cl-case org-clubhouse-create-stories-with-labels + ('t tags) + ('existing (-filter (lambda (tag) (-some (lambda (l) + (string-equal tag (cdr l))) + (org-clubhouse-labels))) + tags))))))) + +;;; +;;; API integration +;;; + +(defvar org-clubhouse-base-url* "https://api.clubhouse.io/api/v3") + +(defun org-clubhouse-auth-url (url &optional params) + (concat url + "?" + (url-build-query-string + (cons `("token" ,org-clubhouse-auth-token) params)))) + +(defun org-clubhouse-baseify-url (url) + (if (s-starts-with? org-clubhouse-base-url* url) url + (concat org-clubhouse-base-url* + (if (s-starts-with? "/" url) url + (concat "/" url))))) + +(cl-defun org-clubhouse-request (method url &key data (params '())) + (message "%s %s %s" method url (prin1-to-string data)) + (let* ((url-request-method method) + (url-request-extra-headers + '(("Content-Type" . "application/json"))) + (url-request-data data) + (buf)) + + (setq url (-> url + org-clubhouse-baseify-url + (org-clubhouse-auth-url params))) + + (setq buf (url-retrieve-synchronously url)) + + (with-current-buffer buf + (goto-char url-http-end-of-headers) + (prog1 (json-read) (kill-buffer))))) + +(cl-defun to-id-name-pairs + (seq &optional (id-attr 'id) (name-attr 'name)) + (->> seq + ->list + (-map (lambda (resource) + (cons (alist-get id-attr resource) + (alist-get name-attr resource)))))) + +(cl-defun org-clubhouse-fetch-as-id-name-pairs + (resource &optional + (id-attr 'id) + (name-attr 'name)) + "Returns the given resource from clubhouse as (id . name) pairs" + (let ((resp-json (org-clubhouse-request "GET" resource))) + (-> resp-json + ->list + reject-archived + (to-id-name-pairs id-attr name-attr)))) + +(defun org-clubhouse-get-story + (clubhouse-id) + (org-clubhouse-request "GET" (format "/stories/%s" clubhouse-id))) + +(defun org-clubhouse-link-to-story (story-id) + (format "https://app.clubhouse.io/%s/story/%d" + org-clubhouse-team-name + story-id)) + +(defun org-clubhouse-link-to-epic (epic-id) + (format "https://app.clubhouse.io/%s/epic/%d" + org-clubhouse-team-name + epic-id)) + +(defun org-clubhouse-link-to-milestone (milestone-id) + (format "https://app.clubhouse.io/%s/milestone/%d" + org-clubhouse-team-name + milestone-id)) + +(defun org-clubhouse-link-to-project (project-id) + (format "https://app.clubhouse.io/%s/project/%d" + org-clubhouse-team-name + project-id)) + +;;; +;;; Caching +;;; + +(comment + (defcache org-clubhouse-projects + (org-sync-clubhouse-fetch-as-id-name-pairs "projectx")) + + (clear-org-clubhouse-projects-cache) + (clear-org-clubhouse-cache) + ) + +(defvar org-clubhouse-cache-clear-functions ()) + +(defmacro defcache (name &optional docstring &rest body) + (let* ((doc (when docstring (list docstring))) + (cache-var-name (intern (concat (symbol-name name) + "-cache"))) + (clear-cache-function-name + (intern (concat "clear-" (symbol-name cache-var-name))))) + `(progn + (defvar ,cache-var-name :no-cache) + (defun ,name () + ,@doc + (when (equal :no-cache ,cache-var-name) + (setq ,cache-var-name (progn ,@body))) + ,cache-var-name) + (defun ,clear-cache-function-name () + (interactive) + (setq ,cache-var-name :no-cache)) + + (push (quote ,clear-cache-function-name) + org-clubhouse-cache-clear-functions)))) + +(defun org-clubhouse-clear-cache () + (interactive) + (-map #'funcall org-clubhouse-cache-clear-functions)) + +;;; +;;; API resource functions +;;; + +(defcache org-clubhouse-projects + "Returns projects as (project-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "projects")) + +(defcache org-clubhouse-epics + "Returns epics as (epic-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "epics")) + +(defcache org-clubhouse-milestones + "Returns milestone-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "milestones")) + +(defcache org-clubhouse-workflow-states + "Returns worflow states as (name . id) pairs" + (let* ((resp-json (org-clubhouse-request "GET" "workflows")) + (workflows (->list resp-json)) + ;; just assume it exists, for now + (workflow (-find (lambda (workflow) + (equal org-clubhouse-workflow-name + (alist-get 'name workflow))) + workflows)) + (states (->list (alist-get 'states workflow)))) + (to-id-name-pairs states + 'name + 'id))) + +(defcache org-clubhouse-labels + "Returns labels as (label-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "labels")) + +(defcache org-clubhouse-whoami + "Returns the ID of the logged in user" + (->> (org-clubhouse-request + "GET" + "/members") + ->list + (find-if (lambda (m) + (->> m + (alist-get 'profile) + (alist-get 'mention_name) + (equal org-clubhouse-username)))) + (alist-get 'id))) + +(defcache org-clubhouse-iterations + "Returns iterations as (iteration-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "iterations")) + +(defun org-clubhouse-stories-in-project (project-id) + "Return the stories in the given PROJECT-ID as org headlines." + (let ((resp-json (org-clubhouse-request "GET" (format "/projects/%d/stories" project-id)))) + (->> resp-json ->list reject-archived + (-reject (lambda (story) (equal :json-true (alist-get 'completed story)))) + (-map (lambda (story) + (cons + (cons 'status + (cond + ((equal :json-true (alist-get 'started story)) + 'started) + ((equal :json-true (alist-get 'completed story)) + 'completed) + ('t + 'open))) + story))) + (-map (-partial #'alist->plist + '((name . :title) + (id . :id) + (status . :status))))))) + +(defun org-clubhouse-workflow-state-id-to-todo-keyword (workflow-state-id) + "Convert the named clubhouse WORKFLOW-STATE-ID to an org todo keyword." + (let* ((state-name (alist-get-equal + workflow-state-id + (invert-alist (org-clubhouse-workflow-states)))) + (inv-state-name-alist + (-map (lambda (cell) (cons (cdr cell) (car cell))) + org-clubhouse-state-alist))) + (or (alist-get-equal state-name inv-state-name-alist) + (if state-name (s-upcase state-name) "UNKNOWN")))) + +;;; +;;; Prompting +;;; + +(defun org-clubhouse-prompt-for-project (cb) + (ivy-read + "Select a project: " + (-map #'cdr (org-clubhouse-projects)) + :require-match t + :history 'org-clubhouse-project-history + :action (lambda (selected) + (let ((project-id + (find-match-in-alist selected (org-clubhouse-projects)))) + (funcall cb project-id))))) + +(defun org-clubhouse-prompt-for-epic (cb) + "Prompt the user for an epic using ivy and call CB with its ID." + (ivy-read + "Select an epic: " + (-map #'cdr (append '((nil . "No Epic")) (org-clubhouse-epics))) + :history 'org-clubhouse-epic-history + :action (lambda (selected) + (let ((epic-id + (find-match-in-alist selected (org-clubhouse-epics)))) + (funcall cb epic-id))))) + +(defun org-clubhouse-prompt-for-milestone (cb) + "Prompt the user for a milestone using ivy and call CB with its ID." + (ivy-read + "Select a milestone: " + (-map #'cdr (append '((nil . "No Milestone")) (org-clubhouse-milestones))) + :require-match t + :history 'org-clubhouse-milestone-history + :action (lambda (selected) + (let ((milestone-id + (find-match-in-alist selected (org-clubhouse-milestones)))) + (funcall cb milestone-id))))) + +(defun org-clubhouse-prompt-for-story-type (cb) + (ivy-read + "Select a story type: " + (-map #'cdr org-clubhouse-story-types) + :history 'org-clubhouse-story-history + :action (lambda (selected) + (let ((story-type + (find-match-in-alist selected org-clubhouse-story-types))) + (funcall cb story-type))))) + +(defun org-clubhouse-prompt-for-default-story-type () + (interactive) + (ivy-read + "Select a default story type: " + (-map #'cdr org-clubhouse-default-story-types) + :history 'org-clubhouse-default-story-history + :action (lambda (selected) + (let ((story-type + (find-match-in-alist selected org-clubhouse-default-story-types))) + (if (string-equal story-type "prompt") + (setq org-clubhouse-default-story-type nil) + (setq org-clubhouse-default-story-type story-type)))))) + +;;; +;;; Epic creation +;;; + +(cl-defun org-clubhouse-create-epic-internal + (title &key milestone-id) + (cl-assert (and (stringp title) + (or (null milestone-id) + (integerp milestone-id)))) + (org-clubhouse-request + "POST" + "epics" + :data + (json-encode + `((name . ,title) + (milestone_id . ,milestone-id))))) + +(defun org-clubhouse-populate-created-epic (elt epic) + (let ((elt-start (plist-get elt :begin)) + (epic-id (alist-get 'id epic)) + (milestone-id (alist-get 'milestone_id epic))) + (save-excursion + (goto-char elt-start) + + (org-set-property "clubhouse-epic-id" + (org-link-make-string + (org-clubhouse-link-to-epic epic-id) + (number-to-string epic-id))) + + (when milestone-id + (org-set-property "clubhouse-milestone" + (org-link-make-string + (org-clubhouse-link-to-milestone milestone-id) + (alist-get milestone-id (org-clubhouse-milestones)))))))) + +(defun org-clubhouse-create-epic (&optional beg end) + "Creates a clubhouse epic using selected headlines. +Will pull the title from the headline at point, or create epics for all the +headlines in the selected region. + +All epics are added to the same milestone, as selected via a prompt. +If the epics already have a CLUBHOUSE-EPIC-ID, they are filtered and ignored." + (interactive + (when (use-region-p) + (list (region-beginning region-end)))) + + (let* ((elts (org-clubhouse-collect-headlines beg end)) + (elts (-remove (lambda (elt) (plist-get elt :CLUBHOUSE-EPIC-ID)) elts))) + (org-clubhouse-prompt-for-milestone + (lambda (milestone-id) + (dolist (elt elts) + (let* ((title (plist-get elt :title)) + (epic (org-clubhouse-create-epic-internal + title + :milestone-id milestone-id))) + (org-clubhouse-populate-created-epic elt epic)) + elts))))) + +;;; +;;; Story creation +;;; + +(defun org-clubhouse-default-state-id () + (alist-get-equal org-clubhouse-default-state (org-clubhouse-workflow-states))) + +(cl-defun org-clubhouse-create-story-internal + (title &key project-id epic-id story-type description labels) + (cl-assert (and (stringp title) + (integerp project-id) + (or (null epic-id) (integerp epic-id)) + (or (null description) (stringp description)))) + (let ((workflow-state-id (org-clubhouse-default-state-id)) + (params `((name . ,title) + (project_id . ,project-id) + (epic_id . ,epic-id) + (story_type . ,story-type) + (description . ,(or description "")) + (labels . ,labels)))) + + (when workflow-state-id + (push `(workflow_state_id . ,workflow-state-id) params)) + + (org-clubhouse-request + "POST" + "stories" + :data + (json-encode params)))) + +(cl-defun org-clubhouse-populate-created-story (elt story &key extra-properties) + (let ((elt-start (plist-get elt :begin)) + (story-id (alist-get 'id story)) + (epic-id (alist-get 'epic_id story)) + (project-id (alist-get 'project_id story)) + (story-type (alist-get 'story_type story))) + + (save-excursion + (goto-char elt-start) + + (org-set-property "clubhouse-id" + (org-link-make-string + (org-clubhouse-link-to-story story-id) + (number-to-string story-id))) + (when epic-id + (org-set-property "clubhouse-epic" + (org-link-make-string + (org-clubhouse-link-to-epic epic-id) + (alist-get epic-id (org-clubhouse-epics))))) + + (org-set-property "clubhouse-project" + (org-link-make-string + (org-clubhouse-link-to-project project-id) + (alist-get project-id (org-clubhouse-projects)))) + + (org-set-property "story-type" + (alist-get-equal story-type org-clubhouse-story-types)) + + (dolist (extra-prop extra-properties) + (org-set-property (car extra-prop) + (alist-get (cdr extra-prop) story))) + + (org-todo "TODO")))) + +(defun org-clubhouse-create-story (&optional beg end &key then) + "Creates a clubhouse story using selected headlines. + +Will pull the title from the headline at point, +or create cards for all the headlines in the selected region. + +All stories are added to the same project and epic, as selected via two prompts. +If the stories already have a CLUBHOUSE-ID, they are filtered and ignored." + (interactive + (when (use-region-p) + (list (region-beginning) (region-end)))) + + (let* ((elts (org-clubhouse-collect-headlines beg end)) + (new-elts (-remove (lambda (elt) (plist-get elt :CLUBHOUSE-ID)) elts))) + (org-clubhouse-prompt-for-project + (lambda (project-id) + (when project-id + (org-clubhouse-prompt-for-epic + (lambda (epic-id) + (let ((create-story + (lambda (story-type) + (-map + (lambda (elt) + (let* ((title (plist-get elt :title)) + (description + (save-mark-and-excursion + (goto-char (plist-get elt :begin)) + (org-clubhouse-find-description-drawer))) + (labels (org-clubhouse--labels-for-elt elt)) + (story (org-clubhouse-create-story-internal + title + :project-id project-id + :epic-id epic-id + :story-type story-type + :description description + :labels labels))) + (org-clubhouse-populate-created-story elt story) + (when (functionp then) + (funcall then story)))) + new-elts)))) + (if org-clubhouse-default-story-type + (funcall create-story org-clubhouse-default-story-type) + (org-clubhouse-prompt-for-story-type create-story)))))))))) + +(defun org-clubhouse-create-story-with-task-list (&optional beg end) + "Creates a clubhouse story using the selected headline, making all direct +children of that headline into tasks in the task list of the story." + (interactive + (when (use-region-p) + (list (region-beginning) (region-end)))) + + (let* ((elt (org-element-and-children-at-point))) + (org-clubhouse-create-story nil nil + :then (lambda (story) + (pp story) + (org-clubhouse-push-task-list + (alist-get 'id story) + (plist-get elt :children)))))) + +;;; +;;; Task creation +;;; + +(cl-defun org-clubhouse-create-task (title &key story-id) + (cl-assert (and (stringp title) + (integerp story-id))) + (org-clubhouse-request + "POST" + (format "/stories/%d/tasks" story-id) + :data (json-encode `((description . ,title))))) + +(defun org-clubhouse-push-task-list (&optional parent-clubhouse-id child-elts) + "Writes each child of the element at point as a task list item. + +When called as (org-clubhouse-push-task-list PARENT-CLUBHOUSE-ID CHILD-ELTS), +allows manually passing a clubhouse ID and list of org-element plists to write" + (interactive) + (let* ((elt (org-element-and-children-at-point)) + (parent-clubhouse-id (or parent-clubhouse-id + (org-element-extract-clubhouse-id elt))) + (child-elts (or child-elts (plist-get elt :children))) + (story (org-clubhouse-get-story parent-clubhouse-id)) + (existing-tasks (alist-get 'tasks story)) + (task-exists + (lambda (task-name) + (cl-some (lambda (task) + (string-equal task-name (alist-get 'description task))) + existing-tasks))) + (elts-with-starts + (-map (lambda (e) (cons (set-marker (make-marker) + (plist-get e :begin)) + e)) + child-elts))) + (dolist (child-elt-and-start elts-with-starts) + (let* ((start (car child-elt-and-start)) + (child-elt (cdr child-elt-and-start)) + (task-name (plist-get child-elt :title))) + (unless (funcall task-exists task-name) + (let ((task (org-clubhouse-create-task + task-name + :story-id parent-clubhouse-id))) + (org-clubhouse-populate-created-task child-elt task start))))))) + +(defun org-clubhouse-populate-created-task (elt task &optional begin) + (let ((elt-start (or begin (plist-get elt :begin))) + (task-id (alist-get 'id task)) + (story-id (alist-get 'story_id task))) + + (save-excursion + (goto-char elt-start) + + (org-set-property "clubhouse-task-id" (format "%d" task-id)) + + (org-set-property "clubhouse-story-id" + (org-link-make-string + (org-clubhouse-link-to-story story-id) + (number-to-string story-id))) + + (org-todo "TODO")))) + +;;; +;;; Task Updates +;;; + +(cl-defun org-clubhouse-update-task-internal + (story-id task-id &rest attrs) + (cl-assert (and (integerp story-id) + (integerp task-id) + (listp attrs))) + (org-clubhouse-request + "PUT" + (format "stories/%d/tasks/%d" story-id task-id) + :data + (json-encode attrs))) + +;;; +;;; Story updates +;;; + +(cl-defun org-clubhouse-update-story-internal + (story-id &rest attrs) + (cl-assert (and (integerp story-id) + (listp attrs))) + (org-clubhouse-request + "PUT" + (format "stories/%d" story-id) + :data + (json-encode attrs))) + +(cl-defun org-clubhouse-update-story-at-point (&rest attrs) + (when-let* ((clubhouse-id (org-element-clubhouse-id))) + (apply + #'org-clubhouse-update-story-internal + (cons clubhouse-id attrs)) + t)) + +(defun org-clubhouse-update-story-title () + "Update the title of the Clubhouse story linked to the current headline. + +Update the title of the story linked to the current headline with the text of +the headline." + (interactive) + + (let* ((elt (org-element-find-headline)) + (title (plist-get elt :title)) + (clubhouse-id (org-element-clubhouse-id))) + (and + (org-clubhouse-update-story-at-point + clubhouse-id + :name title) + (message "Successfully updated story title to \"%s\"" + title)))) + +(defun org-clubhouse-update-status () + "Update the status of the Clubhouse story linked to the current element. + +Update the status of the Clubhouse story linked to the current element with the +entry in `org-clubhouse-state-alist' corresponding to the todo-keyword of the +element." + (interactive) + (let* ((elt (org-element-find-headline)) + (todo-keyword (-> elt + (plist-get :todo-keyword) + (substring-no-properties))) + + (clubhouse-id (org-element-extract-clubhouse-id elt)) + (task-id (plist-get elt :CLUBHOUSE-TASK-ID))) + (cond + (clubhouse-id + (let* ((todo-keyword (-> elt + (plist-get :todo-keyword) + (substring-no-properties)))) + (when-let* ((clubhouse-workflow-state + (alist-get-equal todo-keyword org-clubhouse-state-alist)) + (workflow-state-id + (alist-get-equal clubhouse-workflow-state + (org-clubhouse-workflow-states)))) + (let ((update-assignee? + (if (or (eq 't org-clubhouse-claim-story-on-status-update) + (member todo-keyword + org-clubhouse-claim-story-on-status-update)) + (if org-clubhouse-username + 't + (warn "Not claiming story since `org-clubhouse-username' + is not set") + nil)))) + + (if update-assignee? + (org-clubhouse-update-story-internal + clubhouse-id + :workflow_state_id workflow-state-id + :owner_ids (if update-assignee? + (list (org-clubhouse-whoami)) + (list))) + (org-clubhouse-update-story-internal + clubhouse-id + :workflow_state_id workflow-state-id)) + (message + (if update-assignee? + "Successfully claimed story and updated clubhouse status to \"%s\"" + "Successfully updated clubhouse status to \"%s\"") + clubhouse-workflow-state))))) + + (task-id + (let ((story-id (org-element-extract-clubhouse-id + elt + :CLUBHOUSE-STORY-ID)) + (done? (member todo-keyword org-done-keywords))) + (org-clubhouse-update-task-internal + story-id + (string-to-number task-id) + :complete (if done? 't :json-false)) + (message "Successfully marked clubhouse task status as %s" + (if done? "complete" "incomplete"))))))) + +(defun org-clubhouse-update-description () + "Update the description of the Clubhouse story linked to the current element. + +Update the status of the Clubhouse story linked to the current element with the +contents of a drawer inside the element called DESCRIPTION, if any." + (interactive) + (when-let* ((new-description (org-clubhouse-find-description-drawer))) + (and + (org-clubhouse-update-story-at-point + :description new-description) + (message "Successfully updated story description")))) + +(defun org-clubhouse-update-labels () + "Update the labels of the Clubhouse story linked to the current element. + +Will use the value of `org-clubhouse-create-stories-with-labels' to determine +which labels to set." + (interactive) + (when-let* ((elt (org-element-find-headline)) + (new-labels (org-clubhouse--labels-for-elt elt))) + (and + (org-clubhouse-update-story-at-point + :labels new-labels) + (message "Successfully updated story labels to :%s:" + (->> new-labels + (-map #'cdar) + (s-join ":")))))) + + +;;; +;;; Creating headlines from existing stories +;;; + +(defun org-clubhouse--task-to-headline-text (level task) + (format "%s %s %s +:PROPERTIES: +:clubhouse-task-id: %s +:clubhouse-story-id: %s +:END:" + (make-string level ?*) + (if (equal :json-false (alist-get 'complete task)) + "TODO" "DONE") + (alist-get 'description task) + (alist-get 'id task) + (let ((story-id (alist-get 'story_id task))) + (org-link-make-string + (org-clubhouse-link-to-story story-id) + story-id)))) + +(defun org-clubhouse--story-to-headline-text (level story) + (let ((story-id (alist-get 'id story))) + (format + "%s %s %s %s +:PROPERTIES: +:clubhouse-id: %s +:END: +%s +%s +" + (make-string level ?*) + (org-clubhouse-workflow-state-id-to-todo-keyword + (alist-get 'workflow_state_id story)) + (alist-get 'name story) + (if-let ((labels (->> story + (alist-get 'labels) + ->list + (-map (apply-partially #'alist-get 'name))))) + (format ":%s:" (s-join ":" labels)) + "") + (org-link-make-string + (org-clubhouse-link-to-story story-id) + (number-to-string story-id)) + (let ((desc (alist-get 'description story))) + (if (= 0 (length desc)) "" + (format ":DESCRIPTION:\n%s\n:END:" desc))) + (if-let ((tasks (seq-sort-by + (apply-partially #'alist-get 'position) + #'< + (or (alist-get 'tasks story) + (alist-get 'tasks + (org-clubhouse-get-story story-id)))))) + (mapconcat (apply-partially #'org-clubhouse--task-to-headline-text + (1+ level)) + tasks + "\n") + "")))) + +(defun org-clubhouse-headline-from-my-tasks (level) + "Prompt my active stories and create a single `org-mode' headline at LEVEL." + (interactive "*nLevel: \n") + (if org-clubhouse-username + (let* ((story-list (org-clubhouse--search-stories + (format "owner:%s !is:done !is:archived" + org-clubhouse-username))) + (stories (to-id-name-pairs story-list))) + (org-clubhouse-headline-from-story-id level + (find-match-in-alist + (ivy-read "Select Story: " + (-map #'cdr stories)) + stories))) + (warn "Can't fetch my tasks if `org-clubhouse-username' is unset"))) + +(defun org-clubhouse-headline-from-story-id (level story-id) + "Create a single `org-mode' headline at LEVEL based on the given clubhouse STORY-ID." + (interactive "*nLevel: \nnStory ID: ") + (let* ((story (org-clubhouse-get-story story-id))) + (if (equal '((message . "Resource not found.")) story) + (message "Story ID not found: %d" story-id) + (save-mark-and-excursion + (insert (org-clubhouse--story-to-headline-text level story)) + (org-align-tags))))) + +(defun org-clubhouse--search-stories (query) + (unless (string= "" query) + (-> (org-clubhouse-request "GET" "search/stories" :params `((query ,query))) + cdadr + (append nil) + reject-archived))) + +(defun org-clubhouse-prompt-for-iteration (cb) + "Prompt for iteration and call CB with that iteration" + (ivy-read + "Select an interation: " + (-map #'cdr (org-clubhouse-iterations)) + :require-match t + :history 'org-clubhouse-iteration-history + :action (lambda (selected) + (let ((iteration-id + (find-match-in-alist selected (org-clubhouse-iterations)))) + (funcall cb iteration-id))))) + +(defun org-clubhouse--get-iteration (iteration-id) + (-> (org-clubhouse-request "GET" (format "iterations/%d/stories" iteration-id)) + (append nil))) + +(defun org-clubhouse-headlines-from-iteration (level) + "Create `org-mode' headlines from a clubhouse iteration. + +Create `org-mode' headlines from all the resulting stories at headline level LEVEL." + (interactive "*nLevel: ") + (org-clubhouse-prompt-for-iteration + (lambda (iteration-id) + (let ((story-list (org-clubhouse--get-iteration iteration-id))) + (if (null story-list) + (message "Iteration id returned no stories: %d" iteration-id) + (let ((text (mapconcat (apply-partially + #'org-clubhouse--story-to-headline-text + level) + (reject-archived story-list) "\n"))) + (save-mark-and-excursion + (insert text) + (org-align-all-tags)) + text)))))) + +(defun org-clubhouse-headlines-from-query (level query) + "Create `org-mode' headlines from a clubhouse query. + +Submits QUERY to clubhouse, and creates `org-mode' headlines from all the +resulting stories at headline level LEVEL." + (interactive + "*nLevel: \nMQuery: ") + (let* ((story-list (org-clubhouse--search-stories query))) + (if (null story-list) + (message "Query returned no stories: %s" query) + (let ((text (mapconcat (apply-partially + #'org-clubhouse--story-to-headline-text + level) + (reject-archived story-list) "\n"))) + (if (called-interactively-p) + (save-mark-and-excursion + (insert text) + (org-align-all-tags)) + text))))) + +(defun org-clubhouse-prompt-for-story (cb) + "Prompt the user for a clubhouse story, then call CB with the full story." + (ivy-read "Story title: " + (lambda (search-term) + (let* ((stories (org-clubhouse--search-stories + (if search-term (format "\"%s\"" search-term) + "")))) + (-map (lambda (story) + (propertize (alist-get 'name story) 'story story)) + stories))) + :dynamic-collection t + :history 'org-clubhouse-story-prompt + :action (lambda (s) (funcall cb (get-text-property 0 'story s))) + :require-match t)) + +(defun org-clubhouse-headline-from-story (level) + "Prompt for a story, and create an org headline at LEVEL from that story." + (interactive "*nLevel: ") + (org-clubhouse-prompt-for-story + (lambda (story) + (save-mark-and-excursion + (insert (org-clubhouse--story-to-headline-text level story)) + (org-align-tags))))) + + +(defun org-clubhouse-link () + "Link the current `org-mode' headline with an existing clubhouse story." + (interactive) + (org-clubhouse-prompt-for-story + (lambda (story) + (org-clubhouse-populate-created-story + (org-element-find-headline) + story + :extra-properties '(("clubhouse-story-name" . name))) + (org-todo + (org-clubhouse-workflow-state-id-to-todo-keyword + (alist-get 'workflow_state_id story)))))) + +(defun org-clubhouse-claim () + "Assign the clubhouse story associated with the headline at point to yourself." + (interactive) + (if org-clubhouse-username + (and + (org-clubhouse-update-story-at-point + :owner_ids (list (org-clubhouse-whoami))) + (message "Successfully claimed story")) + (warn "Can't claim story if `org-clubhouse-username' is unset"))) + +(defun org-clubhouse-sync-status (&optional beg end) + "Pull the status(es) for the story(ies) in region and update the todo state. + +Uses `org-clubhouse-state-alist'. Operates over stories from BEG to END" + (interactive + (when (use-region-p) + (list (region-beginning) (region-end)))) + (let ((elts (-filter (lambda (e) (plist-get e :CLUBHOUSE-ID)) + (org-clubhouse-collect-headlines beg end)))) + (save-mark-and-excursion + (dolist (e elts) + (goto-char (plist-get e :begin)) + (let* ((clubhouse-id (org-element-extract-clubhouse-id e)) + (story (org-clubhouse-get-story clubhouse-id)) + (workflow-state-id (alist-get 'workflow_state_id story)) + (todo-keyword (org-clubhouse-workflow-state-id-to-todo-keyword + workflow-state-id))) + (let ((org-after-todo-state-change-hook + (remove 'org-clubhouse-update-status + org-after-todo-state-change-hook))) + (org-todo todo-keyword))))) + (message "Successfully synchronized status of %d stories from Clubhouse" + (length elts)))) + +(cl-defun org-clubhouse-set-epic (&optional story-id epic-id cb &key beg end) + "Set the epic of clubhouse story STORY-ID to EPIC-ID, then call CB. + +When called interactively, prompt for an epic and set the story of the clubhouse +stor{y,ies} at point or region" + (interactive + (when (use-region-p) + (list nil nil nil + :beg (region-beginning) + :end (region-end)))) + (if (and story-id epic-id) + (progn + (org-clubhouse-update-story-internal + story-id :epic-id epic-id) + (when cb (funcall cb))) + (let ((elts (-filter (lambda (elt) (plist-get elt :CLUBHOUSE-ID)) + (org-clubhouse-collect-headlines beg end)))) + (org-clubhouse-prompt-for-epic + (lambda (epic-id) + (-map + (lambda (elt) + (let ((story-id (org-element-extract-clubhouse-id elt))) + (org-clubhouse-set-epic + story-id epic-id + (lambda () + (org-set-property + "clubhouse-epic" + (org-link-make-string + (org-clubhouse-link-to-epic epic-id) + (alist-get epic-id (org-clubhouse-epics)))) + (message "Successfully set the epic on story %d to %d" + story-id epic-id)))))) + elts))))) + +;;; + +(define-minor-mode org-clubhouse-mode + "If enabled, updates to the todo keywords on org headlines will update the +linked ticket in Clubhouse." + :group 'org + :lighter "Org-Clubhouse" + :keymap '() + (add-hook 'org-after-todo-state-change-hook + 'org-clubhouse-update-status + nil + t)) + +(provide 'org-clubhouse) + +;;; org-clubhouse.el ends here diff --git a/users/grfn/resume/chimera.png b/users/grfn/resume/chimera.png new file mode 100644 index 000000000000..6dde989c53b0 --- /dev/null +++ b/users/grfn/resume/chimera.png Binary files differdiff --git a/users/grfn/resume/collection.sty b/users/grfn/resume/collection.sty new file mode 100644 index 000000000000..4f1540a9d214 --- /dev/null +++ b/users/grfn/resume/collection.sty @@ -0,0 +1,85 @@ +%% start of file `collection.sty'. +%% Copyright 2013-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{collection}[2013/03/28 v1.0.0 collections] + + +%------------------------------------------------------------------------------- +% requirements +%------------------------------------------------------------------------------- + + +\RequirePackage{ifthen} + + +%------------------------------------------------------------------------------- +% code +%------------------------------------------------------------------------------- + +% creates a new collection +% usage: \collectionnew{<collection name>} +\newcommand*{\collectionnew}[1]{% + \newcounter{collection@#1@count}} + +% adds an item to a collection +% usage: \collectionadd[<optional key>]{<collection name>}{<item to add>} +\newcommand*{\collectionadd}[3][]{% + \expandafter\def\csname collection@#2@item\roman{collection@#2@count}\endcsname{#3}% + \if\relax\noexpand#1\relax% if #1 is empty + \else\expandafter\def\csname collection@#2@key\roman{collection@#2@count}\endcsname{#1}\fi% + \stepcounter{collection@#2@count}} + +% returns the number of items in a collection +% usage: \collectioncount{<collection name>} +\newcommand*{\collectioncount}[1]{% + \value{collection@#1@count}} + +% gets an item from a collection +% usage: \collectiongetitem{<collection name>}{<element id>} +% where <element id> is an integer between 0 and (collectioncount-1) +\newcommand*{\collectiongetitem}[2]{% + \csname collection@#1@item\romannumeral #2\endcsname} + +% gets a key from a collection +% usage: \collectiongetkey{<collection name>}{<element id>} +% where <element id> is an integer between 0 and (collectioncount-1) +\newcommand*{\collectiongetkey}[2]{% + \csname collection@#1@key\romannumeral #2\endcsname} + +% loops through a collection and perform the given operation on every element +% usage: \collectionloop{<collection name>}{<operation sequence>} +% where <operation sequence> is the code sequence to be evaluated for each collection item, +% code which can refer to \collectionloopid, \collectionloopkey, \collectionloopitem and +% \collectionloopbreak +\newcounter{collection@iterator} +\newcommand*{\collectionloopbreak}{\let\iterate\relax} +\newcommand*{\collectionloop}[2]{% + \setcounter{collection@iterator}{0}% + \loop\ifnum\value{collection@iterator}<\value{collection@#1@count}% + \def\collectionloopid{\arabic{collection@iterator}}% + \def\collectionloopitem{\collectiongetitem{#1}{\collectionloopid}}% + \def\collectionloopkey{\collectiongetkey{#1}{\collectionloopid}}% + #2% + \stepcounter{collection@iterator}% + \repeat} + +% loops through a collection and finds the (first) element matching the given key +% usage: \collectionfindbykey{<collection name>}{key>} +\newcommand*{\collectionfindbykey}[2]{% + \collectionloop{#1}{% + \ifthenelse{\equal{\collectionloopkey}{#2}}{\collectionloopitem\collectionloopbreak}{}}} + + +\endinput + + +%% end of file `collection.cls'. diff --git a/users/grfn/resume/default.nix b/users/grfn/resume/default.nix new file mode 100644 index 000000000000..2db6a650bc6e --- /dev/null +++ b/users/grfn/resume/default.nix @@ -0,0 +1,37 @@ +{ pkgs, ... }: + +with pkgs.lib; + +pkgs.runCommandNoCC "resume.pdf" { + buildInputs = [(pkgs.texlive.combine { + inherit (pkgs.texlive) + capt-of + collection-fontsrecommended + enumitem + etoolbox + fancyvrb + float + fncychap + framed + l3packages + microtype + needspace + parskip + scheme-basic + tabulary + titlesec + ulem + upquote + varwidth + wrapfig + xcolor + ; + })]; +} '' + cp ${builtins.filterSource (path: type: + type == "regular" && + any (ext: hasSuffix ext path) [".sty" ".cls" ".tex" ".png"] + ) ./.}/* . + pdflatex ./resume.tex + cp resume.pdf $out +'' diff --git a/users/grfn/resume/helvetica.sty b/users/grfn/resume/helvetica.sty new file mode 100644 index 000000000000..dacc129a1025 --- /dev/null +++ b/users/grfn/resume/helvetica.sty @@ -0,0 +1,32 @@ +%% +%% This is file `helvetica.sty', based on helvet.sty extended to include +%% definitions for rm and tt. This means commands such as \textbf, \textit, +%% etc. will appear in Helvetica. +%% Changes added by Harriet Borton on <1995/12/11> +%% +%% The original source files were: +%% +%% psfonts.dtx (with options: `helvet') +%% +%% Copyright (C) 1994 Sebastian Rahtz +%% All rights reserved. +%% +%% The original file is part of the PSNFSS2e package. +%% ----------------------------------------- +%% +%% This is a generated file. Permission is granted to to customize the +%% declarations in this file to serve the needs of your installation. +%% However, no permission is granted to distribute a modified version of +%% this file under its original name. +\def\fileversion{4.2} +\def\filedate{94/11/11} +\def\docdate {94/11/06} +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{helvetica}[\filedate\space\fileversion\space +Helvetica PSNFSS2e package] +\renewcommand{\sfdefault}{phv} +\renewcommand{\rmdefault}{phv} +\renewcommand{\ttdefault}{pcr} +\endinput +%% +%% End of file `helvetica.sty'. diff --git a/users/grfn/resume/moderncv.cls b/users/grfn/resume/moderncv.cls new file mode 100644 index 000000000000..a40f80733736 --- /dev/null +++ b/users/grfn/resume/moderncv.cls @@ -0,0 +1,585 @@ +%% start of file `moderncv.cls'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesClass{moderncv}[2013/02/09 v1.3.0 modern curriculum vitae and letter document class] + + +%------------------------------------------------------------------------------- +% class options +% +% (need to be done before the external package loading, for example because +% we need \paperwidth, \paperheight and \@ptsize to be defined before loading +% geometry and fancyhdr) +%------------------------------------------------------------------------------- +% paper size option +\DeclareOption{a4paper}{ + \setlength\paperheight{297mm} + \setlength\paperwidth{210mm}} +\DeclareOption{a5paper}{ + \setlength\paperheight{210mm} + \setlength\paperwidth{148mm}} +\DeclareOption{b5paper}{ + \setlength\paperheight{250mm} + \setlength\paperwidth{176mm}} +\DeclareOption{letterpaper}{ + \setlength\paperheight{11in} + \setlength\paperwidth{8.5in}} +\DeclareOption{legalpaper}{ + \setlength\paperheight{14in} + \setlength\paperwidth{8.5in}} +\DeclareOption{executivepaper}{ + \setlength\paperheight{10.5in} + \setlength\paperwidth{7.25in}} +\DeclareOption{landscape}{ + \setlength\@tempdima{\paperheight} + \setlength\paperheight{\paperwidth} + \setlength\paperwidth{\@tempdima}} + +% font size options +\newcommand\@ptsize{} +\DeclareOption{10pt}{\renewcommand\@ptsize{0}} +\DeclareOption{11pt}{\renewcommand\@ptsize{1}} +\DeclareOption{12pt}{\renewcommand\@ptsize{2}} + +% font type options +\DeclareOption{sans}{\AtBeginDocument{\renewcommand{\familydefault}{\sfdefault}}} +\DeclareOption{roman}{\AtBeginDocument{\renewcommand{\familydefault}{\rmdefault}}} + +% draft/final option +\DeclareOption{draft}{\setlength\overfullrule{5pt}} +\DeclareOption{final}{\setlength\overfullrule{0pt}} + +% execute default options +\ExecuteOptions{a4paper,11pt,final} + +% process given options +\ProcessOptions\relax +\input{size1\@ptsize.clo} + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +% \AtEndPreamble hook (loading etoolbox instead of defining the macro, as to avoid incompatibilities with etoolbox (and packages relying on it) defining the macro too) +\RequirePackage{etoolbox} +%\let\@endpreamblehook\@empty +%\def\AtEndPreamble{\g@addto@macro\@endpreamblehook} +%\let\document@original\document +%\def\document{\endgroup\@endpreamblehook\begingroup\document@original} + +% if... then... else... constructs +\RequirePackage{ifthen} +% TODO: move to xifthen and \isempty{<arg>} instead of \equal{<arg>}{} + +% color +\RequirePackage{xcolor} + +% font loading +%\RequirePackage{ifxetex,ifluatex} +%\newif\ifxetexorluatex +%\ifxetex +% \xetexorluatextrue +%\else +% \ifluatex +% \xetexorluatextrue +% \else +% \xetexorluatexfalse +% \fi +%\fi +% automatic loading of latin modern fonts +%\ifxetexorluatex +% \RequirePackage{fontspec} +% \defaultfontfeatures{Ligatures=TeX} +% \RequirePackage{unicode-math} +% \setmainfont{Latin Modern} +% \setsansfont{Latin Modern Sans} +% \setmathfont{Latin Modern Math} +%\else + \RequirePackage[T1]{fontenc} + \IfFileExists{lmodern.sty}% + {\RequirePackage{lmodern}}% + {} +%\fi + +% hyper links (hyperref is loaded at the end of the preamble to pass options required by loaded packages such as CJK) +\newcommand*\pdfpagemode{UseNone}% do not show thumbnails or bookmarks on opening (on supporting browsers); set \pdfpagemode to "UseOutlines" to show bookmarks +\RequirePackage{url} +\urlstyle{tt} +\AtEndPreamble{ + \pagenumbering{arabic}% has to be issued before loading hyperref, as to set \thepage and hence to avoid hyperref issuing a warning and setting pdfpagelabels=false + \RequirePackage[unicode]{hyperref}% unicode is required for unicode pdf metadata + \hypersetup{ + breaklinks, + baseurl = http://, + pdfborder = 0 0 0, + pdfpagemode = \pdfpagemode, + pdfstartpage = 1, + pdfcreator = {\LaTeX{} with 'moderncv' package}, +% pdfproducer = {\LaTeX{}},% will/should be set automatically to the correct TeX engine used + bookmarksopen = true, + bookmarksdepth= 2,% to show sections and subsections + pdfauthor = {\@firstname{}~\@lastname{}}, + pdftitle = {\@firstname{}~\@lastname{} -- \@title{}}, + pdfsubject = {Resum\'{e} of \@firstname{}~\@lastname{}}, + pdfkeywords = {\@firstname{}~\@lastname{}, curriculum vit\ae{}, resum\'{e}}}} + +% graphics +\RequirePackage{graphicx} + +% headers and footers +\RequirePackage{fancyhdr} +\fancypagestyle{plain}{ + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0pt} + \fancyhf{}} +% page numbers in footer if more than 1 page +\newif\if@displaypagenumbers\@displaypagenumberstrue +\newcommand*{\nopagenumbers}{\@displaypagenumbersfalse} +\AtEndPreamble{% + \AtBeginDocument{% + \if@displaypagenumbers% + \@ifundefined{r@lastpage}{}{% + \ifthenelse{\pageref{lastpage}>1}{% + \newlength{\pagenumberwidth}% + \settowidth{\pagenumberwidth}{\color{color2}\addressfont\itshape\strut\thepage/\pageref{lastpage}}% + \fancypagestyle{plain}{% + \fancyfoot[r]{\parbox[b]{\pagenumberwidth}{\color{color2}\pagenumberfont\strut\thepage/\pageref{lastpage}}}}% the parbox is required to ensure alignment with a possible center footer (e.g., as in the casual style) + \pagestyle{plain}}{}}% + \AtEndDocument{\label{lastpage}}\else\fi}} +\pagestyle{plain} + +% reduced list spacing +% package providing hooks into lists +% originally developped by Jakob Schiรธtz (see http://dcwww.camd.dtu.dk/~schiotz/comp/LatexTips/tweaklist.sty) +% modified and distributed with moderncv(not available otherwise on ctan) +\RequirePackage{tweaklist} +\renewcommand*{\itemhook}{% + \@minipagetrue% removes spacing before lists as they use \addvspace, which doesn't add vertical space inside minipages + \@noparlisttrue% removes spacing at end of lists, caused by \par + \setlength{\topsep}{0pt}% normally not required thanks to \@minipagetrue + \setlength{\partopsep}{0pt}% normally not required thanks to \@minipagetrue + \setlength{\parsep}{0pt}% not required when \itemsep and \parskip are set to 0pt (?) + \setlength{\parskip}{0pt}% + \setlength{\itemsep}{0pt}} +\renewcommand*{\enumhook}{\itemhook{}} +\renewcommand*{\deschook}{\itemhook{}} + +% lengths calculations +\RequirePackage{calc} + +% advanced command arguments (LaTeX 3) +\RequirePackage{xparse} +% TODO (?): replace all \newcommand by \NewDocumentCommand + +% micro-typography (e.g., character protrusion, font expansion, hyphenatable letterspacing) +\RequirePackage{microtype} + +% compatibility package with older versions of moderncv +\RequirePackageWithOptions{moderncvcompatibility} + + +%------------------------------------------------------------------------------- +% class definition +%------------------------------------------------------------------------------- +% minimal base settings +\setlength\lineskip{1\p@} +\setlength\normallineskip{1\p@} +\renewcommand\baselinestretch{} +\setlength{\parindent}{0\p@} +\setlength{\parskip}{0\p@} +\setlength\columnsep{10\p@} +\setlength\columnseprule{0\p@} +\setlength\fboxsep{3\p@} +\setlength\fboxrule{.4\p@} +\setlength\arrayrulewidth{.4\p@} +\setlength\doublerulesep{2\p@} + +% not set on purpose +%\setlength\arraycolsep{5\p@} +%\setlength\tabcolsep{6\p@} +%\setlength\tabbingsep{\labelsep} + +\raggedbottom +\onecolumn + + +%------------------------------------------------------------------------------- +% overall design commands definitions +%------------------------------------------------------------------------------- +% elements +% defines one's name +% usage: \name{<firstname>}{<lastname>} +\newcommand*{\name}[2]{\def\@firstname{#1}\def\@lastname{#2}} +% defines one's title (optional) +% usage: \title{<title>} +\renewcommand*{\title}[1]{\def\@title{#1}} +% defines one's address (optional) +% usage: \address{<street>}{<city>}{<country>} +% where the <city> and <country> arguments can be omitted or provided empty +\NewDocumentCommand{\address}{mG{}G{}}{\def\@addressstreet{#1}\def\@addresscity{#2}\def\@addresscountry{#3}} +% adds a mobile/fixed/fax number to one's personal information (optional) +% usage: \phone[<optional type>]{<number>} +% where <optional type> should be either "mobile", "fixed" or "fax +\RequirePackage{collection} +\collectionnew{phones} +\newcommand*{\phone}[2][fixed]{\collectionadd[#1]{phones}{#2}} +\newcommand*{\email}[1]{\def\@email{#1}} +% defines one's home page (optional) +% usage: \homepage{<url>} +\newcommand*{\homepage}[1]{\def\@homepage{#1}} +% defines one's github (optional) +% usage: \homepage{<url>} +\newcommand*{\github}[1]{\def\@github{#1}} +% defines additional personal information (optional) +% usage: \extrainfo{<text>} +\newcommand*{\extrainfo}[1]{\def\@extrainfo{#1}} + +% colors +\definecolor{color0}{rgb}{0,0,0}% main default color, normally left to black +\definecolor{color1}{rgb}{0,0,0}% primary theme color +\definecolor{color2}{rgb}{0,0,0}% secondary theme color +\definecolor{color3}{rgb}{0,0,0}% tertiary theme color + +% symbols +% itemize labels (the struts were added to correct inter-item spacing (works for single line items, until a solution is found for multi-line ones...) +\newcommand*{\labelitemi}{\strut\textcolor{color1}{\large\rmfamily\textbullet}}% the \rmfamily is required to force Latin Modern fonts when using sans serif, as OMS/lmss/m/n is not defined and gets substituted by OMS/cmsy/m/n +\newcommand*{\labelitemii}{\strut\textcolor{color1}{\large\bfseries-}} +\newcommand*{\labelitemiii}{\strut\textcolor{color1}{\rmfamily\textperiodcentered}}% alternative: \textasteriskcentered; the \rmfamily is required to force Latin Modern fonts when using sans serif, as OMS/lmss/m/n is not defined and gets substituted by OMS/cmsy/m/n +\newcommand*{\labelitemiv}{\labelitemiii} +% enumerate labels +\renewcommand{\theenumi}{\@arabic\c@enumi} +\renewcommand{\theenumii}{\@alph\c@enumii} +\renewcommand{\theenumiii}{\@roman\c@enumiii} +\renewcommand{\theenumiv}{\@Alph\c@enumiv} +% other symbols +\newcommand*{\listitemsymbol}{\labelitemi~} +\newcommand*{\addresssymbol}{} +\newcommand*{\mobilephonesymbol}{} +\newcommand*{\fixedphonesymbol}{} +\newcommand*{\faxphonesymbol}{} +\newcommand*{\emailsymbol}{} +\newcommand*{\homepagesymbol}{} + +% fonts +\AtBeginDocument{\normalfont\color{color0}} + +% strings for internationalisation +\newcommand*{\refname}{Publications} +\newcommand*{\enclname}{Enclosure} + +% makes the footer (normally used both for the resume and the letter) +% usage: \makefooter +\newcommand*{\makefooter}{}% + +% loads a style variant +% usage: \moderncvstyle{<style variant name>} +\newcommand*{\moderncvstyle}[1]{ + \RequirePackage{moderncvstyle#1}} + +% loads a color scheme +% usage: \moderncvcolor{<color scheme name>} +\newcommand*{\moderncvcolor}[1]{ + \RequirePackage{moderncvcolor#1}} + +% loads an icons set +% usage: \moderncvicons{<icon set name>} +\newcommand*{\moderncvicons}[1]{ + \RequirePackage{moderncvicons#1}} + +% recomputes all automatic lengths +\newcommand*{\recomputelengths}{\recomputecvlengths} +\AtBeginDocument{\recomputelengths{}} + +% creates a length if not yet defined +\newcommand*{\@initializelength}[1]{% + \ifdefined#1\else\newlength{#1}\fi} + + +%------------------------------------------------------------------------------- +% resume design commands definitions +%------------------------------------------------------------------------------- +% elements +% defines one's picture (optional) +% usage: photo[<picture width>][<picture frame thickness>]{<picture filename>} +\NewDocumentCommand{\photo}{O{64pt}O{0.4pt}m}{\def\@photowidth{#1}\def\@photoframewidth{#2}\def\@photo{#3}} +\newcommand*{\quote}[1]{\def\@quote{#1}} + +% fonts +\newcommand*{\namefont}{} +\newcommand*{\titlefont}{} +\newcommand*{\addressfont}{} +\newcommand*{\quotefont}{} +\newcommand*{\sectionfont}{} +\newcommand*{\subsectionfont}{} +\newcommand*{\hintfont}{} +\newcommand*{\pagenumberfont}{\addressfont\itshape} + +% styles +\newcommand*{\namestyle}[1]{{\namefont#1}} +\newcommand*{\titlestyle}[1]{{\titlefont#1}} +\newcommand*{\addressstyle}[1]{{\addressfont#1}} +\newcommand*{\quotestyle}[1]{{\quotefont#1}} +\newcommand*{\sectionstyle}[1]{{\sectionfont#1}} +\newcommand*{\subsectionstyle}[1]{{\subsectionfont#1}} +\newcommand*{\hintstyle}[1]{{\hintfont#1}} +\newcommand*{\pagenumberstyle}[1]{{\pagenumberfont#1}} + +% recompute all resume lengths +\newcommand*{\recomputecvlengths}{} + +% internal maketitle command to issue a new line only when required +\newif\if@firstdetailselement\@firstdetailselementtrue +\newcommand*{\makenewline}{ + \if@firstdetailselement% + \strut% to ensure baseline alignment, e.g. with when put in the margin vs sections that also contains a \strut + \else% + \\\fi% + \@firstdetailselementfalse} + +% makes the resume title +% usage: \makecvtitle +\newcommand*{\makecvtitle}{} + +% makes the resume footer +% usage: \makecvfooter +\newcommand*{\makecvfooter}{\makefooter} + +% makes a resume section +% usage: \section{<title>} +% identical starred and non-starred variants should be defined for compatibility with other packages (e.g. with natbib, that uses \section*{} for the bibliography header) +\NewDocumentCommand{\section}{sm}{} + +% makes a resume subsection +% usage: \subsection{title} +\NewDocumentCommand{\subsection}{sm}{} + +% makes a resume line with a header and a corresponding text +% usage: \cvitem[spacing]{header}{text} +\newcommand*{\cvitem}[3][.25em]{} + +% makes a resume line 2 headers and their corresponding text +% usage: \cvdoubleitem[spacing]{header1}{text1}{header2}{text2} +\newcommand*{\cvdoubleitem}[5][.25em]{} + +% makes a resume line with a list item +% usage: \cvlistitem[label]{item} +\newcommand*{\cvlistitem}[2][\listitemsymbol]{} + +% makes a resume line with 2 list items +% usage: \cvlistdoubleitem[label]{item1}{item2} +\newcommand*{\cvlistdoubleitem}[3][\listitemsymbol]{} + +% makes a typical resume job / education entry +% usage: \cventry[spacing]{years}{degree/job title}{institution/employer}{localization}{optionnal: grade/...}{optional: comment/job description} +\newcommand*{\cventry}[7][.25em]{} + +% makes a resume entry with a proficiency comment +% usage: \cvitemwithcomment[spacing]{header}{text}{comment} +\newcommand*{\cvitemwithcomment}[4][.25em]{} + +% makes a generic hyperlink +% usage: \link[optional text]{link} +\newcommand*{\link}[2][]{% + \ifthenelse{\equal{#1}{}}% + {\href{#2}{#2}}% + {\href{#2}{#1}}} + +% makes a http hyperlink +% usage: \httplink[optional text]{link} +\newcommand*{\httplink}[2][]{% + \ifthenelse{\equal{#1}{}}% + {\href{http://#2}{#2}}% + {\href{http://#2}{#1}}} + +% makes an email hyperlink +% usage: \emaillink[optional text]{link} +\newcommand*{\emaillink}[2][]{% + \ifthenelse{\equal{#1}{}}% + {\href{mailto:#2}{#2}}% + {\href{mailto:#2}{#1}}} + +% cvcolumns environment, where every column is created through \cvcolumn +% usage: \begin{cvcolumns} +% \cvcolumn[width]{head}{content} +% \cvcolumn[width]{head}{content} +% ... +% \end{cvcolumns} +% where "width" is the width as a fraction of the line length (between 0 and 1), "head" is the column header and "content" its content +\newcounter{cvcolumnscounter}% counter for the number of columns +\newcounter{cvcolumnsautowidthcounter}% counter for the number of columns with no column width provided, and which will then be equally distributed +\newcounter{tmpiteratorcounter}% counter for any temporary purpose (e.g., iterating loops) +\newlength{\cvcolumnsdummywidth}\setlength{\cvcolumnsdummywidth}{1000pt}% dummy width for total width, in order to enable arithmetics (TeX has no float variables, only integer counters or lengths) +\newlength{\cvcolumnswidth}% total width available for head / content +\newlength{\cvcolumnsautowidth}% total width of columns with no explicit width provided +\newlength{\cvcolumnautowidth}% width of one of the columns with no explicit width provided (based on equal distribution of remaining space) +\newif\if@cvcolumns@head@empty% whether or not at least one of the columns has a header +\newenvironment*{cvcolumns}% + {% at environment opening: reset counters, lengths and ifs + \setcounter{cvcolumnscounter}{0}% + \setcounter{cvcolumnsautowidthcounter}{0}% + \setlength{\cvcolumnsautowidth}{\cvcolumnsdummywidth}% + \setlength{\cvcolumnautowidth}{0pt}% + \@cvcolumns@head@emptytrue}% + {% at environment closing: typeset environment + % compute the width of each cvcolumn, considering a spacing of \separatorcolumnwidth and the columns with set width + \ifnum\thecvcolumnscounter>0% + \setlength{\cvcolumnswidth}{\maincolumnwidth-\value{cvcolumnscounter}\separatorcolumnwidth+\separatorcolumnwidth}% + \setlength{\cvcolumnautowidth}{\cvcolumnswidth*\ratio{\cvcolumnsautowidth}{\cvcolumnsdummywidth}/\value{cvcolumnsautowidthcounter}}\fi% + % pre-aggregate the tabular definition, heading and content (required before creating the tabular, as the tabular environment doesn't like loops --- probably because "&" generates a \endgroup) + % - the tabular definition is the aggregation of the different "\cvcolumn<i>@def" (by default "p{\cvcolumnautowidth}"), separated by "@{\hspace*{\separatorcolumnwidth}}" + % - the tabular heading is the aggregation of the different "\cvcolumn<i>@head", separated by "&" + % - the tabular content is the aggregation of the different "\cvcolumn<i>@content", separated by "&" + % to aggregate the different elements, \protected@edef or \g@addto@macro is required to avoid that \cvcolumns@def, -@head and -@content get expanded in subsequent redefinitions, which would cause errors due to the expansions of \hspace, of \subsectionstyle and possibly of user content/argument such as font commands + \def\cvcolumns@def{}% + \def\cvcolumns@head{}% + \def\cvcolumns@content{}% + \setcounter{tmpiteratorcounter}{0}% + % loop based on \g@addto@macro + \loop\ifnum\thetmpiteratorcounter<\thecvcolumnscounter% + \ifnum\thetmpiteratorcounter=0\else% + \g@addto@macro\cvcolumns@def{@{\hspace*{\separatorcolumnwidth}}}% + \g@addto@macro\cvcolumns@head{&}% + \g@addto@macro\cvcolumns@content{&}\fi% + \expandafter\g@addto@macro\expandafter\cvcolumns@def\expandafter{\csname cvcolumn\roman{tmpiteratorcounter}@def\endcsname}% + \expandafter\g@addto@macro\expandafter\cvcolumns@head\expandafter{\csname cvcolumn\roman{tmpiteratorcounter}@head\endcsname}% + \expandafter\g@addto@macro\expandafter\cvcolumns@content\expandafter{\csname cvcolumn\roman{tmpiteratorcounter}@content\endcsname}% + \stepcounter{tmpiteratorcounter}% + \repeat% +% % same loop based on \protected@edef +% \loop\ifnum\thetmpiteratorcounter<\thecvcolumnscounter% +% \ifnum\thetmpiteratorcounter=0\else% +% \protected@edef\cvcolumns@def{\cvcolumns@def @{\hspace*{\separatorcolumnwidth}}}% +% \protected@edef\cvcolumns@head{\cvcolumns@head &}% +% \protected@edef\cvcolumns@content{\cvcolumns@content &}\fi% +% \expandafter\protected@edef\expandafter\cvcolumns@def\expandafter{\expandafter\cvcolumns@def\expandafter\protect\csname cvcolumn\roman{tmpiteratorcounter}@def\endcsname}% +% \expandafter\protected@edef\expandafter\cvcolumns@head\expandafter{\expandafter\cvcolumns@head\expandafter\protect\csname cvcolumn\roman{tmpiteratorcounter}@head\endcsname}% +% \expandafter\protected@edef\expandafter\cvcolumns@content\expandafter{\expandafter\cvcolumns@content\expandafter\protect\csname cvcolumn\roman{tmpiteratorcounter}@content\endcsname}% +% \stepcounter{tmpiteratorcounter}% +% \repeat% + % create the tabular + \cvitem{}{% + \begin{tabular}{\cvcolumns@def}% + \if@cvcolumns@head@empty\else% + \cvcolumns@head\\[-.8em]% + {\color{color1}\rule{\maincolumnwidth}{.25pt}}\\\fi% + \cvcolumns@content% + \end{tabular}}} + +% cvcolumn command, to create a column inside a cvcolumns environment +% usage: \cvcolumn[width]{head}{content} +% where "width" is the width as a fraction of the line length (between 0 and 1), "head" is the column header and "content" its content ("head" and "content" can contain "\\", "\newline" or any other paragraph command such as "itemize") +\newcommand*{\cvcolumn}[3][\cvcolumnautowidth]{% +% \def\cvcolumn@width{}% + \ifthenelse{\equal{#1}{\cvcolumnautowidth}}% + {% if no width fraction is provided, count this column as auto-adjusted and set its width to \cvcolumnsautowidth + \stepcounter{cvcolumnsautowidthcounter}% + \expandafter\expandafter\expandafter\def\expandafter\csname cvcolumn\roman{cvcolumnscounter}@def\endcsname{p{\cvcolumnautowidth}}% + \expandafter\expandafter\expandafter\def\expandafter\csname cvcolumn\roman{cvcolumnscounter}@head\endcsname{\protect\parbox[b]{\cvcolumnautowidth}{\protect\subsectionstyle{#2}}}}% + {% if a width is provided, set the width of the column to it and decrease the available space for auto-adjusted columns + \addtolength{\cvcolumnsautowidth}{-#1\cvcolumnsdummywidth}% + \expandafter\expandafter\expandafter\def\expandafter\csname cvcolumn\roman{cvcolumnscounter}@def\endcsname{p{#1\cvcolumnswidth}}% + \expandafter\expandafter\expandafter\def\expandafter\csname cvcolumn\roman{cvcolumnscounter}@head\endcsname{\protect\parbox[b]{#1\cvcolumnswidth}{\protect\subsectionstyle{#2}}}}% + \ifthenelse{\equal{#2}{}}{}{\@cvcolumns@head@emptyfalse}% + \expandafter\expandafter\expandafter\def\expandafter\csname cvcolumn\roman{cvcolumnscounter}@content\endcsname{\protect\cvcolumncell{#3}}% + \stepcounter{cvcolumnscounter}} + +% internal cvcolumncell command, that enables a cvcolumn cell to contain paragraph commands (lists, newlines, etc) +\newcommand*{\cvcolumncell}[1]{{% put cell inside a group, so that command redefinitions are only local + % roughly restore \\ to its regular definition (outside of tabular) + \renewcommand*{\\}{\newline}% + % enclose the contents of the cell inside a vertical box, to allow paragraph commands + \protect\vtop{#1}}} + +% thebibliography environment, for use with BibTeX and possibly multibib +\newlength{\bibindent} +\setlength{\bibindent}{1.5em} +% bibliography item label +\newcommand*{\bibliographyitemlabel}{}% use \@biblabel{\arabic{enumiv}} for BibTeX labels +%\newif\if@multibibfirstbib\@multibibfirstbibfalse +% bibliography head (section, etc}, depending on whether multibib is used +\newcommand*{\bibliographyhead}[1]{\section{#1}} +\AtEndPreamble{\@ifpackageloaded{multibib}{\renewcommand*{\bibliographyhead}[1]{\subsection{#1}}}{}} +% thebibliography environment definition +\newenvironment{thebibliography}[1]{}{} +\newcommand*{\newblock}{\hskip .11em\@plus.33em\@minus.07em} +\let\@openbib@code\@empty + +% itemize, enumerate and description environment +\setlength{\leftmargini} {1em} +\leftmargin\leftmargini +\setlength{\leftmarginii} {\leftmargini} +\setlength{\leftmarginiii} {\leftmargini} +\setlength{\leftmarginiv} {\leftmargini} +\setlength{\leftmarginv} {\leftmargini} +\setlength{\leftmarginvi} {\leftmargini} +\setlength{\labelsep} {.5em}% this is the distance between the label and the body, but it pushes the label to the left rather than pushing the body to the right (to do the latter, modify \leftmargin(i) +\setlength{\labelwidth} {\leftmargini}% unfortunately, \labelwidth is not defined by item level (i.e. no \labeliwidth, \labeliiwidth, etc) +\addtolength{\labelwidth} {-\labelsep} +\@beginparpenalty -\@lowpenalty +\@endparpenalty -\@lowpenalty +\@itempenalty -\@lowpenalty +\newcommand\labelenumi{\theenumi.} +\newcommand\labelenumii{(\theenumii)} +\newcommand\labelenumiii{\theenumiii.} +\newcommand\labelenumiv{\theenumiv.} +\renewcommand\p@enumii{\theenumi} +\renewcommand\p@enumiii{\p@enumii(\theenumii)} +\renewcommand\p@enumiv{\p@enumiii\theenumiii} +% description label +\newcommand*\descriptionlabel[1]{\hspace\labelsep\normalfont\bfseries#1} + +% classical \today definition +\def\today{\ifcase\month\or + January\or February\or March\or April\or May\or June\or + July\or August\or September\or October\or November\or December\fi + \space\number\day, \number\year} + +%\newcommand{\widthofautobox}[1]{% +% \widthof{\begin{tabular}{@{}l@{}}#1\end{tabular}}} + +%\newcommand{\autobox}[2][b]{% +% \parbox[#1]{\widthofautobox{#2}}{#2}} + + +%------------------------------------------------------------------------------- +% letter design commands definitions +%------------------------------------------------------------------------------- +% elements +\newcommand*{\recipient}[2]{\def\@recipientname{#1}\def\@recipientaddress{#2}} +\renewcommand*{\date}[1]{\def\@date{#1}}\date{\today} +\newcommand*{\opening}[1]{\def\@opening{#1}} +\newcommand*{\closing}[1]{\def\@closing{#1}} +\newcommand*{\enclosure}[2][]{% + % if an optional argument is provided, use it to redefine \enclname + \ifthenelse{\equal{#1}{}}{}{\renewcommand*{\enclname}{#1}}% + \def\@enclosure{#2}} + +% recompute all letter lengths +\newcommand*{\recomputeletterlengths}{} + +% makes the letter title +% usage: \makelettertitle +\newcommand*{\makelettertitle}{} + +% makes the letter footer +% usage: \makeletterfooter +\newcommand*{\makeletterfooter}{\makefooter} + +% makes the letter closing +% usage: \makeletterclosing +\newcommand*{\makeletterclosing}{} + + +\endinput + + +%% end of file `moderncv.cls'. diff --git a/users/grfn/resume/moderncvcolorblack.sty b/users/grfn/resume/moderncvcolorblack.sty new file mode 100644 index 000000000000..3a6e1477f322 --- /dev/null +++ b/users/grfn/resume/moderncvcolorblack.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorblack.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorblack}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: black] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0,0,0}% black +\definecolor{color2}{rgb}{0,0,0}% black + + +\endinput + + +%% end of file `moderncvcolorblack.sty'. diff --git a/users/grfn/resume/moderncvcolorblue.sty b/users/grfn/resume/moderncvcolorblue.sty new file mode 100644 index 000000000000..7b949c704acd --- /dev/null +++ b/users/grfn/resume/moderncvcolorblue.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorblue.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorblue}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: blue] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.22,0.45,0.70}% light blue +\definecolor{color2}{rgb}{0.45,0.45,0.45}% dark grey + + +\endinput + + +%% end of file `moderncvcolorblue.sty'. diff --git a/users/grfn/resume/moderncvcolorgreen.sty b/users/grfn/resume/moderncvcolorgreen.sty new file mode 100644 index 000000000000..4de7f848a04e --- /dev/null +++ b/users/grfn/resume/moderncvcolorgreen.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorgreen.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorgreen}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: green] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.35,0.70,0.30}% green +\definecolor{color2}{rgb}{0.45,0.45,0.45}% dark grey + + +\endinput + + +%% end of file `moderncvcolorgreen.sty'. diff --git a/users/grfn/resume/moderncvcolorgrey.sty b/users/grfn/resume/moderncvcolorgrey.sty new file mode 100644 index 000000000000..9018726a2384 --- /dev/null +++ b/users/grfn/resume/moderncvcolorgrey.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorgrey.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorgrey}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: grey] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.55,0.55,0.55}% dark grey +\definecolor{color2}{rgb}{0.55,0.55,0.55}% dark grey + + +\endinput + + +%% end of file `moderncvcolorgrey.sty'. diff --git a/users/grfn/resume/moderncvcolororange.sty b/users/grfn/resume/moderncvcolororange.sty new file mode 100644 index 000000000000..134ae2401133 --- /dev/null +++ b/users/grfn/resume/moderncvcolororange.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolororange.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolororange}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: orange] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.95,0.55,0.15}% orange +\definecolor{color2}{rgb}{0.45,0.45,0.45}% dark grey + + +\endinput + + +%% end of file `moderncvcolororange.sty'. diff --git a/users/grfn/resume/moderncvcolorpurple.sty b/users/grfn/resume/moderncvcolorpurple.sty new file mode 100644 index 000000000000..d3dc5345b080 --- /dev/null +++ b/users/grfn/resume/moderncvcolorpurple.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorpurple.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorpurple}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: purple] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.50,0.33,0.80}% purple +\definecolor{color2}{rgb}{0.45,0.45,0.45}% dark grey + + +\endinput + + +%% end of file `moderncvcolorpurple.sty'. diff --git a/users/grfn/resume/moderncvcolorred.sty b/users/grfn/resume/moderncvcolorred.sty new file mode 100644 index 000000000000..681181997d38 --- /dev/null +++ b/users/grfn/resume/moderncvcolorred.sty @@ -0,0 +1,27 @@ +%% start of file `moderncvcolorred.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcolorred}[2013/02/09 v1.3.0 modern curriculum vitae and letter color scheme: red] + + +%------------------------------------------------------------------------------- +% color scheme definition +%------------------------------------------------------------------------------- +\definecolor{color0}{rgb}{0,0,0}% black +\definecolor{color1}{rgb}{0.95,0.20,0.20}% red +\definecolor{color2}{rgb}{0.45,0.45,0.45}% dark grey + + +\endinput + + +%% end of file `moderncvcolorred.sty'. diff --git a/users/grfn/resume/moderncvcompatibility.sty b/users/grfn/resume/moderncvcompatibility.sty new file mode 100644 index 000000000000..1fc53f2180e1 --- /dev/null +++ b/users/grfn/resume/moderncvcompatibility.sty @@ -0,0 +1,104 @@ +%% start of file `moderncvcompatibility.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvcompatibility}[2013/02/09 v1.3.0 modern curriculum vitae and letter compatibility patches] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- + + +%------------------------------------------------------------------------------- +% package options +%------------------------------------------------------------------------------- +% old casual option (version 0.1) +%\DeclareOption{casual}{\input{moderncvstylecasual.sty}} + +% old classic option (version 0.1) +%\DeclareOption{classic}{\input{moderncvstyleclassic.sty}} + +\DeclareOption*{} + +% process given options +\ProcessOptions\relax + +%------------------------------------------------------------------------------- +% definitions +%------------------------------------------------------------------------------- +% compatibility with version 0.1 +\newcommand*{\cvresume}[2]{\cvlistdoubleitem{#1}{#2}} + +% compatibility with versions <= 0.2 +% section, cvline, ... with width argument... +%\newcommand*{\section}[2][0.825]{% +% \closesection{}% +% \@sectionopentrue% +% \addcontentsline{toc}{part}{#2} +% \begin{longtable}[t]{@{}r@{\hspace{.025\textwidth}}@{}p{#1\textwidth}@{}}% +%% \colorrule{.15\textwidth}&\mbox{\color{sectiontitlecolor}\sectionfont#2}\\[1ex]}% +% {\color{sectionrectanglecolor}\rule{0.15\textwidth}{1ex}}&\mbox{\color{sectiontitlecolor}\sectionfont#2}\\[1ex]}% +%\newcommand*{\cvline}[3][.825]{% +% \begin{minipage}[t]{\hintscolumnwidth}\raggedleft\small\sffamily#2\end{minipage}&\begin{minipage}[t]{\maincolumnwidth}#3\end{minipage}\\} +%\newcommand*{\cvitem}[3][.825]{% +% \cvline[#1]{#2}{#3\vspace*{.75em}}} % the \vspace*{} inside the cvline environment is a hack... (should conceptually be outside the environment) + +% compatibility with versions <= 0.5 +%\newcommand*{\cvitem}[2]{\cvline{#1}{#2}} +%\newcommand*{\moderncvstyle}[1]{\moderncvtheme{#1}} + +% compatibility with versions <= 0.7 +\newcommand*{\closesection}{} +\newcommand*{\emptysection}{} +\newcommand*{\sethintscolumnlength}[1]{% + \setlength{\hintscolumnwidth}{#1}% + \recomputelengths} +\newcommand*{\sethintscolumntowidth}[1]{% + \settowidth{\hintscolumnwidth}{#1}% + \recomputelengths} + +% compatibility with versions <= 0.15 +\newcommand*{\cvline}[2]{\cvitem{#1}{#2}} +\newcommand*{\cvlanguage}[3]{\cvitemwithcomment{#1}{#2}{#3}} +\newcommand*{\cvcomputer}[4]{\cvdoubleitem{#1}{\small#2}{#3}{\small#4}} +\newcommand*{\moderncvtheme}[2][blue]{% + \moderncvcolor{#1}% + \moderncvstyle{#2}} + +% compatibility with versions <= 0.19 +\newcommand*{\maketitle}{\makecvtitle}% +\title{}% to avoid LaTeX complaining that \maketitle is a called without first a call to \title +\newcommand*{\maketitlenamewidth}{\makecvtitlenamewidth} + +% compatibility with versions <= 1.3.0 +\newcommand*{\firstname}[1]{\def\@firstname{#1}} +\newcommand*{\lastname}[1]{\def\@lastname{#1}} +\newcommand*{\givenname}[1]{\def\@firstname{#1}} +\newcommand*{\familyname}[1]{\def\@lastname{#1}} +\def\@familyname{\@lastname} + +% compatibility with versions <= 1.4.0 +\newcommand*{\mobile}[1]{\collectionadd[mobile]{phones}{#1}} +%\newcommand*{\phone}[1]{\collectionadd[fixed]{phones}{#1}}% implicit, as \phone{...} defaults to \phone[fixed]{...} +\newcommand*{\fax}[1]{\collectionadd[fax]{phones}{#1}} +\newcommand*{\@mobile}{\collectionfindbykey{phones}{mobile}} +\newcommand*{\@phone}{\collectionfindbykey{phones}{fixed}} +\newcommand*{\@fax}{\collectionfindbykey{phones}{fax}} +\newcommand*{\phonesymbol}{\fixedphonesymbol} +\newcommand*{\mobilesymbol}{\mobilephonesymbol} +\newcommand*{\faxsymbol}{\faxphonesymbol} + + +\endinput + + +%% end of file `moderncvcompatibility.sty'. diff --git a/users/grfn/resume/moderncviconsletters.sty b/users/grfn/resume/moderncviconsletters.sty new file mode 100644 index 000000000000..0a4e2864be29 --- /dev/null +++ b/users/grfn/resume/moderncviconsletters.sty @@ -0,0 +1,50 @@ +%% start of file `moderncviconsletters.sty'. +%% Copyright 2013-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncviconsmarvosym}[2013/02/09 v1.3.0 modern curriculum vitae and letter icons: letters] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +% MarVoSym font +%\RequirePackage{marvosym} +\newcommand*{\marvosymbol}[1]{} +%\ifxetexorluatex +% \renewcommand*{\marvosymbol}[1]{{\fontspec{MarVoSym}\char#1}} +%\else + \renewcommand*{\marvosymbol}[1]{{\fontfamily{mvs}\fontencoding{U}\fontseries{m}\fontshape{n}\selectfont\char#1}} +%\fi + + +%------------------------------------------------------------------------------- +% symbols definition +%------------------------------------------------------------------------------- +\renewcommand*{\labelitemi}{\strut\textcolor{color1}{\marvosymbol{123}}}% equivalent to \Neutral from marvosym package; alternative: \fontencoding{U}\fontfamily{ding}\selectfont\tiny\symbol{'102} +%\renewcommand*{\labelitemii}{\strut\textcolor{color1}{\large\bfseries-}}% no change from default in moderncv.cls +%\renewcommand*{\labelitemiii}{\strut\textcolor{color1}{\rmfamily\textperiodcentered}}% no change from default in moderncv.cls +%\renewcommand*{\labelitemiv}{\labelitemiii}% no change from default in moderncv.cls + +\renewcommand*{\addresssymbol}{} +\renewcommand*{\mobilephonesymbol}{\textbf{M}~} +\renewcommand*{\fixedphonesymbol}{\textbf{T}~} +\renewcommand*{\faxphonesymbol}{\textbf{F}~} +\renewcommand*{\emailsymbol}{\textbf{E}~} +\renewcommand*{\homepagesymbol}{} + +\renewcommand*{\listitemsymbol}{\labelitemi~} + + +\endinput + + +%% end of file `moderncviconsletters.sty'. diff --git a/users/grfn/resume/moderncviconsmarvosym.sty b/users/grfn/resume/moderncviconsmarvosym.sty new file mode 100644 index 000000000000..eb1b1ec727bb --- /dev/null +++ b/users/grfn/resume/moderncviconsmarvosym.sty @@ -0,0 +1,48 @@ +%% start of file `moderncviconsmarvosym.sty'. +%% Copyright 2013-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncviconsmarvosym}[2013/02/09 v1.3.0 modern curriculum vitae and letter icons: marvosym] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +% MarVoSym font +%\RequirePackage{marvosym} +\newcommand*{\marvosymbol}[1]{} +%\ifxetexorluatex +% \renewcommand*{\marvosymbol}[1]{{\fontspec{MarVoSym}\char#1}} +%\else + \renewcommand*{\marvosymbol}[1]{{\fontfamily{mvs}\fontencoding{U}\fontseries{m}\fontshape{n}\selectfont\char#1}} +%\fi + + +%------------------------------------------------------------------------------- +% symbols definition +%------------------------------------------------------------------------------- +\renewcommand*{\labelitemi}{\strut\textcolor{color1}{\marvosymbol{123}}}% equivalent to \Neutral from marvosym package; alternative: \fontencoding{U}\fontfamily{ding}\selectfont\tiny\symbol{'102} +%\renewcommand*{\labelitemii}{\strut\textcolor{color1}{\large\bfseries-}}% no change from default in moderncv.cls +%\renewcommand*{\labelitemiii}{\strut\textcolor{color1}{\rmfamily\textperiodcentered}}% no change from default in moderncv.cls +%\renewcommand*{\labelitemiv}{\labelitemiii}% no change from default in moderncv.cls + +\renewcommand*{\addresssymbol}{} +\renewcommand*{\mobilephonesymbol}{\marvosymbol{72}~} +\renewcommand*{\fixedphonesymbol}{\marvosymbol{84}~} +\renewcommand*{\faxphonesymbol}{\marvosymbol{117}~} +\renewcommand*{\emailsymbol}{\marvosymbol{66}~} +\renewcommand*{\homepagesymbol}{{\Large\marvosymbol{205}}~} + + +\endinput + + +%% end of file `moderncviconsmarvosym.sty'. diff --git a/users/grfn/resume/moderncvstylebanking.sty b/users/grfn/resume/moderncvstylebanking.sty new file mode 100644 index 000000000000..fb0b70fdcd10 --- /dev/null +++ b/users/grfn/resume/moderncvstylebanking.sty @@ -0,0 +1,287 @@ +%% start of file `moderncvstylebanking.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvstylebanking}[2013/02/09 v1.3.0 modern curriculum vitae and letter style scheme: banking] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- + + +%------------------------------------------------------------------------------- +% overall style definition +%------------------------------------------------------------------------------- +% fonts +%\ifxetexorluatex +% \setmainfont{Tex-Gyre Pagella} +% \setsansfont{Tex-Gyre Pagella} +% \setmathfont{Tex-Gyre Pagella} +% \setmathfont[range=\mathit,\mathsfit]{Tex-Gyre Pagella Italic} +% \setmathfont[range=\mathbfup,\mathbfsfup]{Tex-Gyre Pagella Bold} +% \setmathfont[range=\mathbfit,\mathbfsfit]{Tex-Gyre Pagella Bold Italic} +%\else + \IfFileExists{tgpagella.sty}% + {% + \RequirePackage{tgpagella}% + \renewcommand*{\familydefault}{\rmdefault}}% + {} +%\fi + +% symbols +\moderncvicons{marvosym} + +% commands +\newcommand*{\maketitlesymbol}{% + {~~~{\rmfamily\textbullet}~~~}}% the \rmfamily is required to force Latin Modern fonts when using sans serif, as OMS/lmss/m/n is not defined and gets substituted by OMS/cmsy/m/n +% internal command to add an element to the footer +% it collects the elements in a temporary box, and checks when to flush the box +\newsavebox{\maketitlebox}% +\newsavebox{\maketitletempbox}% +\newlength{\maketitlewidth}% +\newlength{\maketitleboxwidth}% +\newif\if@firstmaketitleelement\@firstmaketitleelementtrue% +% adds an element to the maketitle, separated by maketitlesymbol +% usage: \addtomaketitle[maketitlesymbol]{element} +\newcommand*{\addtomaketitle}[2][\maketitlesymbol]{% + \if@firstmaketitleelement% + \savebox{\maketitletempbox}{\usebox{\maketitlebox}#2}% + \else% + \savebox{\maketitletempbox}{\usebox{\maketitlebox}#1#2}\fi% + \settowidth{\maketitleboxwidth}{\usebox{\maketitletempbox}}% + \ifnum\maketitleboxwidth<\maketitlewidth% + \savebox{\maketitlebox}{\usebox{\maketitletempbox}}% + \@firstmaketitleelementfalse% + \else% + \flushmaketitle{}\\% + \savebox{\maketitlebox}{#2}% + \savebox{\maketitletempbox}{#2}% + \settowidth{\maketitleboxwidth}{\usebox{\maketitlebox}}% + \@firstmaketitleelementfalse\fi} +% internal command to flush the maketitle +\newcommand*{\flushmaketitle}{% + \strut\usebox{\maketitlebox}% + \savebox{\maketitlebox}{}% + \savebox{\maketitletempbox}{}% + \setlength{\maketitleboxwidth}{0pt}} +\renewcommand*{\maketitle}{% + \setlength{\maketitlewidth}{0.8\textwidth}% + \hfil% + \parbox{\maketitlewidth}{% + \centering% + % name and title + \namestyle{\@firstname~\@lastname}% + \ifthenelse{\equal{\@title}{}}{}{\titlestyle{~|~\@title}}\\% \isundefined doesn't work on \@title, as LaTeX itself defines \@title (before it possibly gets redefined by \title) + % detailed information + \addressfont\color{color2}% + \ifthenelse{\isundefined{\@addressstreet}}{}{\addtomaketitle{\addresssymbol\@addressstreet}% + \ifthenelse{\equal{\@addresscity}{}}{}{\addtomaketitle[~--~]{\@addresscity}}% if \addresstreet is defined, \addresscity and \addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\addtomaketitle[~--~]{\@addresscountry}}% + \flushmaketitle\@firstmaketitleelementtrue\\}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \addtomaketitle{\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}}% + \ifthenelse{\isundefined{\@email}}{}{\addtomaketitle{\emailsymbol\emaillink{\@email}}}% + \ifthenelse{\isundefined{\@homepage}}{}{\addtomaketitle{\homepagesymbol\httplink{\@homepage}}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\addtomaketitle{\@extrainfo}}% + \flushmaketitle}\\[2.5em]}% need to force a \par after this to avoid weird spacing bug at the first section if no blank line is left after \maketitle + + +%------------------------------------------------------------------------------- +% resume style definition +%------------------------------------------------------------------------------- +% fonts +\renewcommand*{\namefont}{\Huge\bfseries\upshape} +\renewcommand*{\titlefont}{\Huge\mdseries\upshape} +\renewcommand*{\addressfont}{\normalsize\mdseries\upshape} +\renewcommand*{\quotefont}{\large\slshape} +\renewcommand*{\sectionfont}{\Large\bfseries\upshape} +\renewcommand*{\subsectionfont}{\large\upshape\fontseries{sb}\selectfont} +\renewcommand*{\hintfont}{\bfseries} + +% styles +\renewcommand*{\namestyle}[1]{{\namefont\textcolor{color1}{#1}}} +\renewcommand*{\titlestyle}[1]{{\titlefont\textcolor{color2!85}{#1}}} +\renewcommand*{\addressstyle}[1]{{\addressfont\textcolor{color1}{#1}}} +\renewcommand*{\quotestyle}[1]{{\quotefont\textcolor{color1}{#1}}} +\renewcommand*{\sectionstyle}[1]{{\sectionfont\textcolor{color1}{#1}}} +\renewcommand*{\subsectionstyle}[1]{{\subsectionfont\textcolor{color1}{#1}}} +\renewcommand*{\hintstyle}[1]{{\hintfont\textcolor{color0}{#1}}} + +% lengths +\newlength{\quotewidth} +\newlength{\hintscolumnwidth} +\setlength{\hintscolumnwidth}{0.3\textwidth}% +\newlength{\separatorcolumnwidth} +\setlength{\separatorcolumnwidth}{0.025\textwidth}% +\newlength{\maincolumnwidth} +\newlength{\doubleitemcolumnwidth} +\newlength{\listitemsymbolwidth} +\settowidth{\listitemsymbolwidth}{\listitemsymbol} +\newlength{\listitemmaincolumnwidth} +\newlength{\listdoubleitemmaincolumnwidth} + +% commands +\renewcommand*{\recomputecvlengths}{% + \setlength{\quotewidth}{0.65\textwidth}% + % main lenghts + \setlength{\maincolumnwidth}{\textwidth}% + % listitem lengths + \setlength{\listitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth}% + % doubleitem lengths + \setlength{\doubleitemcolumnwidth}{\maincolumnwidth-\separatorcolumnwidth}% + \setlength{\doubleitemcolumnwidth}{0.5\doubleitemcolumnwidth}% + % listdoubleitem lengths + \setlength{\listdoubleitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth-\separatorcolumnwidth-\listitemsymbolwidth}% + \setlength{\listdoubleitemmaincolumnwidth}{0.5\listdoubleitemmaincolumnwidth}% + % fancyhdr lengths + \renewcommand{\headwidth}{\textwidth}% + % regular lengths + \setlength{\parskip}{0\p@}} + +\renewcommand*{\makecvtitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputecvlengths% + \maketitle% + % optional quote + \ifthenelse{\isundefined{\@quote}}% + {}% + {{\centering\begin{minipage}{\quotewidth}\centering\quotestyle{\@quote}\end{minipage}\\[2.5em]}}% + \par}% to avoid weird spacing bug at the first section if no blank line is left after \maketitle} + +\RenewDocumentCommand{\section}{sm}{% + \par\addvspace{2.5ex}% + \phantomsection{}% reset the anchor for hyperrefs + \addcontentsline{toc}{section}{#2}% + \strut\sectionstyle{#2}% + {\color{color1}\hrule}% + \par\nobreak\addvspace{1ex}\@afterheading} + +\newcommand{\subsectionfill}{\xleaders\hbox to 0.35em{\scriptsize.}\hfill}% different subsectionfills will not be perfectly aligned, but remaining space at the end of the fill will be distributed evenly between leaders, so it will be barely visible +\RenewDocumentCommand{\subsection}{sm}{% + \par\addvspace{1ex}% + \phantomsection{}% + \addcontentsline{toc}{subsection}{#2}% + \strut\subsectionstyle{#2}{\color{color1}{\subsectionfill}}% + \par\nobreak\addvspace{0.5ex}\@afterheading} + +\renewcommand*{\cvitem}[3][.25em]{% + \ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }{#3}% + \par\addvspace{#1}} + +\renewcommand*{\cvdoubleitem}[5][.25em]{% + \begin{minipage}[t]{\doubleitemcolumnwidth}\hintstyle{#2}: #3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\doubleitemcolumnwidth}\ifthenelse{\equal{#4}{}}{}{\hintstyle{#4}: }#5\end{minipage}% + \par\addvspace{#1}} + +\renewcommand*{\cvlistitem}[2][.25em]{% + \listitemsymbol\begin{minipage}[t]{\listitemmaincolumnwidth}#2\end{minipage}% + \par\addvspace{#1}} + +\renewcommand*{\cvlistdoubleitem}[3][.25em]{% + \cvitem[#1]{}{\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#2\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \ifthenelse{\equal{#3}{}}% + {}% + {\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#3\end{minipage}}}} + +\renewcommand*{\cventry}[7][.25em]{ + \begin{tabular*}{\textwidth}{l@{\extracolsep{\fill}}r}% + {\bfseries #4} & {\bfseries #5} \\% + {\itshape #3\ifthenelse{\equal{#6}{}}{}{, #6}} & {\itshape #2}\\% + \end{tabular*}% + \ifx&% + \else{\\\vbox{\small#7}}\fi% + \par\addvspace{#1}} + +\newbox{\cvitemwithcommentmainbox} +\newlength{\cvitemwithcommentmainlength} +\newlength{\cvitemwithcommentcommentlength} +\renewcommand*{\cvitemwithcomment}[4][.25em]{% + \savebox{\cvitemwithcommentmainbox}{\ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }#3}% + \setlength{\cvitemwithcommentmainlength}{\widthof{\usebox{\cvitemwithcommentmainbox}}}% + \setlength{\cvitemwithcommentcommentlength}{\maincolumnwidth-\separatorcolumnwidth-\cvitemwithcommentmainlength}% + \begin{minipage}[t]{\cvitemwithcommentmainlength}\ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }#3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\cvitemwithcommentcommentlength}\raggedleft\small\itshape#4\end{minipage}% + \par\addvspace{#1}} + +\renewenvironment{thebibliography}[1]% + {% + \bibliographyhead{\refname}% +% \small% + \begin{list}{\bibliographyitemlabel}% + {% + \setlength{\topsep}{0pt}% + \setlength{\labelwidth}{0pt}% + \setlength{\labelsep}{0pt}% + \leftmargin\labelwidth% + \advance\leftmargin\labelsep% + \@openbib@code% + \usecounter{enumiv}% + \let\p@enumiv\@empty% + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \sloppy\clubpenalty4000\widowpenalty4000% +% \sfcode`\.\@m% +% \sfcode `\=1000\relax% + }% + {% + \def\@noitemerr{\@latex@warning{Empty `thebibliography' environment}}% + \end{list}% + } + + +%------------------------------------------------------------------------------- +% letter style definition +%------------------------------------------------------------------------------- +% commands +\renewcommand*{\recomputeletterlengths}{ + \recomputecvlengths% + \setlength{\parskip}{6\p@}} + +\renewcommand*{\makelettertitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputeletterlengths% + % sender block + \maketitle% + \par% + % recipient block + \begin{minipage}[t]{.5\textwidth} + \raggedright% + \addressfont% + {\bfseries\upshape\@recipientname}\\% + \@recipientaddress% + \end{minipage} + % date + \hfill % US style +% \\[1em] % UK style + \@date\\[2em]% US informal style: "April 6, 2006"; UK formal style: "05/04/2006" + % opening + \raggedright% + \@opening\\[1.5em]% + % ensure no extra spacing after \makelettertitle due to a possible blank line +% \ignorespacesafterend% not working + \hspace{0pt}\par\vspace{-\baselineskip}\vspace{-\parskip}} + +\renewcommand*{\makeletterclosing}{ + \@closing\\[3em]% + {\bfseries \@firstname~\@lastname}% + \ifthenelse{\isundefined{\@enclosure}}{}{% + \\% + \vfill% + {\color{color2}\itshape\enclname: \@enclosure}}} + + +\endinput + + +%% end of file `moderncvstylebanking.sty'. diff --git a/users/grfn/resume/moderncvstylecasual.sty b/users/grfn/resume/moderncvstylecasual.sty new file mode 100644 index 000000000000..e375e7612a5a --- /dev/null +++ b/users/grfn/resume/moderncvstylecasual.sty @@ -0,0 +1,182 @@ +%% start of file `moderncvstylecasual.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvstylecasual}[2013/02/09 v1.3.0 modern curriculum vitae and letter style scheme: casual] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +\RequirePackage{moderncvstyleclassic} + + +%------------------------------------------------------------------------------- +% overall style definition +%------------------------------------------------------------------------------- +% commands +% footer symbol used to separate footer elements +\newcommand*{\footersymbol}{% + {~~~{\rmfamily\textbullet}~~~}}% the \rmfamily is required to force Latin Modern fonts when using sans serif, as OMS/lmss/m/n is not defined and gets substituted by OMS/cmsy/m/n +% internal command to add an element to the footer +% it collects the elements in a temporary box, and checks when to flush the box +\newsavebox{\footerbox}% +\newsavebox{\footertempbox}% +\newlength{\footerwidth}% +\newlength{\footerboxwidth}% +\newif\if@firstfooterelement\@firstfooterelementtrue% +% adds an element to the footer, separated by footersymbol +% usage: \addtofooter[footersymbol]{element} +\newcommand*{\addtofooter}[2][\footersymbol]{% + \if@firstfooterelement% + \savebox{\footertempbox}{\usebox{\footerbox}#2}% + \else% + \savebox{\footertempbox}{\usebox{\footerbox}#1#2}\fi% + \settowidth{\footerboxwidth}{\usebox{\footertempbox}}% + \ifnum\footerboxwidth<\footerwidth% + \savebox{\footerbox}{\usebox{\footertempbox}}% + \@firstfooterelementfalse% + \else% + \flushfooter\\% + \savebox{\footerbox}{#2}% + \savebox{\footertempbox}{#2}% + \settowidth{\footerboxwidth}{\usebox{\footerbox}}% + \@firstfooterelementfalse\fi} +% internal command to flush the footer +\newcommand*{\flushfooter}{% + \strut\usebox{\footerbox}% + \savebox{\footerbox}{}% + \savebox{\footertempbox}{}% + \setlength{\footerboxwidth}{0pt}} + + +%------------------------------------------------------------------------------- +% resume style definition +%------------------------------------------------------------------------------- +% fonts +\renewcommand*{\namefont}{\fontsize{38}{40}\mdseries\upshape} +\renewcommand*{\addressfont}{\normalsize\mdseries\slshape} + +% commands +\renewcommand*{\makecvtitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputecvlengths% + % ensure footer with personal information + \makecvfooter% + % optional picture + \newbox{\makecvtitlepicturebox}% + \savebox{\makecvtitlepicturebox}{% + \ifthenelse{\isundefined{\@photo}}% + {}% + {% + \setlength\fboxrule{\@photoframewidth}% + \ifdim\@photoframewidth=0pt% + \setlength{\fboxsep}{0pt}\fi% + {\color{color1}\framebox{\includegraphics[width=\@photowidth]{\@photo}}}}}% + \usebox{\makecvtitlepicturebox}% + % name + \@initializelength{\makecvtitlepicturewidth}% + \settowidth{\makecvtitlepicturewidth}{\usebox{\makecvtitlepicturebox}}% + \parbox[b]{\textwidth-\makecvtitlepicturewidth}{% + \raggedleft\namefont{\color{color2!50}\@firstname} {\color{color2}\@lastname}}\\[-.35em]% alternate design: \MakeLowercase and no space + {\color{color2!50}\rule{\textwidth}{.25ex}}% + % optional title + \ifthenelse{\equal{\@title}{}}{}{\\[1.25em]\null\hfill\titlestyle{\@title}}\\[2.5em]% \null is required as there is no box on the line after \\, so glue (and leaders) disappears; this is in contrast to after \par, where the next line starts with an indent box (even after \noindent). + % optional quote + \ifthenelse{\isundefined{\@quote}}% + {}% + {{\null\hfill\begin{minipage}{\quotewidth}\centering\quotestyle{\@quote}\end{minipage}\hfill\null\\[2.5em]}}% + \par}% to avoid weird spacing bug at the first section if no blank line is left after \maketitle + +\renewcommand*{\makecvfooter}{% + \setlength{\footerwidth}{0.8\textwidth}% + \fancypagestyle{plain}{% + \fancyfoot[c]{% + \parbox[b]{\footerwidth}{% + \centering% + \color{color2}\addressfont% + \ifthenelse{\isundefined{\@addressstreet}}{}{\addtofooter[]{\addresssymbol\@addressstreet}% + \ifthenelse{\equal{\@addresscity}{}}{}{\addtofooter[~--~]{\@addresscity}}% if \addresstreet is defined, \addresscity and \addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\addtofooter[~--~]{\@addresscountry}}% + \flushfooter\@firstfooterelementtrue\\}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \addtofooter{\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}}% + \ifthenelse{\isundefined{\@email}}{}{\addtofooter{\emailsymbol\emaillink{\@email}}}% + \ifthenelse{\isundefined{\@homepage}}{}{\addtofooter{\homepagesymbol\httplink{\@homepage}}}% + \ifthenelse{\isundefined{\@github}}{}{\addtofooter{\httplink{http://github.com/\@github}}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\addtofooter{\@extrainfo}}% + \ifthenelse{\lengthtest{\footerboxwidth=0pt}}{}{\flushfooter}% the lengthtest is required to avoid flushing an empty footer, which could cause a blank line due to the \\ after the address, if no other personal info is used + }}}% + \pagestyle{plain}} + + +%------------------------------------------------------------------------------- +% letter style definition +%------------------------------------------------------------------------------- +\renewcommand*{\makelettertitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputeletterlengths% + % ensure footer with personal information + \makeletterfooter% + % recipient block + \begin{minipage}[t]{.5\textwidth} + \raggedright% + \addressfont% + {\bfseries\upshape\@recipientname}\\% + \@recipientaddress% + \end{minipage} + % date + \hfill% US style +% \\[1em]% UK style + \@date\\[2em]% US informal style: "April 6, 2006"; UK formal style: "05/04/2006" + % opening + \raggedright% + \@opening\\[1.5em]% + % ensure no extra spacing after \makelettertitle due to a possible blank line +% \ignorespacesafterend% not working + \hspace{0pt}\par\vspace{-\baselineskip}\vspace{-\parskip}} + +\renewcommand*{\makeletterfooter}{% + \setlength{\footerwidth}{0.8\textwidth}% + \fancypagestyle{plain}{% + \fancyfoot[c]{% + \parbox[b]{\footerwidth}{% + \centering% + \addressfont\color{color2}% + \vspace{-\baselineskip}% to cancel out the extra vertical space taken by the name (below) and ensure perfect alignment of letter and cv footers + \strut{\bfseries\upshape\@firstname~\@lastname}\\% the \strut is required to ensure the line is exactly \baselineskip tall + \ifthenelse{\isundefined{\@addressstreet}}{}{\addtofooter[]{\addresssymbol\@addressstreet}% + \ifthenelse{\equal{\@addresscity}{}}{}{\addtofooter[~--~]{\@addresscity}}% if \addresstreet is defined, \addresscity and addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\addtofooter[~--~]{\@addresscountry}}% + \flushfooter\@firstfooterelementtrue\\}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \addtofooter{\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}}% + \ifthenelse{\isundefined{\@email}}{}{\addtofooter{\emailsymbol\emaillink{\@email}}}% + \ifthenelse{\isundefined{\@homepage}}{}{\addtofooter{\homepagesymbol\httplink{\@homepage}}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\addtofooter{\@extrainfo}}% + \ifthenelse{\lengthtest{\footerboxwidth=0pt}}{}{\flushfooter}% the lengthtest is required to avoid flushing an empty footer, which could cause a blank line due to the \\ after the address, if no other personal info is used + }}}% + \pagestyle{plain}} + +\renewcommand*{\makeletterclosing}{ + \@closing\\[3em]% + {\bfseries\@firstname~\@lastname}% + \ifthenelse{\isundefined{\@enclosure}}{}{% + \\% + \vfil% + {\color{color2}\itshape\enclname: \@enclosure}}% + \vfil} + + +\endinput + + +%% end of file `moderncvstylecasual.sty'. diff --git a/users/grfn/resume/moderncvstyleclassic.sty b/users/grfn/resume/moderncvstyleclassic.sty new file mode 100644 index 000000000000..63cf97aa3b7d --- /dev/null +++ b/users/grfn/resume/moderncvstyleclassic.sty @@ -0,0 +1,294 @@ +%% start of file `moderncvstyleclassic.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvstyleclassic}[2013/02/09 v1.3.0 modern curriculum vitae and letter style scheme: classic] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +% Latin Modern fonts +%\ifxetexorluatex +% \setmainfont{Latin Modern Roman} +% \setsansfont{Latin Modern Sans} +% \setmathfont{Latin Modern Math} +%\else + \IfFileExists{lmodern.sty}% + {\RequirePackage{lmodern}}% + {} +%\fi + + +%------------------------------------------------------------------------------- +% overall style definition +%------------------------------------------------------------------------------- +% symbols +\moderncvicons{marvosym} + + +%------------------------------------------------------------------------------- +% resume style definition +%------------------------------------------------------------------------------- +% fonts +\renewcommand*{\namefont}{\fontsize{34}{36}\mdseries\upshape} +\renewcommand*{\titlefont}{\LARGE\mdseries\slshape} +\renewcommand*{\addressfont}{\small\mdseries\slshape} +\renewcommand*{\quotefont}{\large\slshape} +\renewcommand*{\sectionfont}{\Large\mdseries\upshape} +\renewcommand*{\subsectionfont}{\large\mdseries\upshape} +\renewcommand*{\hintfont}{} + +% styles +\renewcommand*{\namestyle}[1]{{\namefont\textcolor{color0}{#1}}} +\renewcommand*{\titlestyle}[1]{{\titlefont\textcolor{color2}{#1}}} +\renewcommand*{\addressstyle}[1]{{\addressfont\textcolor{color1}{#1}}} +\renewcommand*{\quotestyle}[1]{{\quotefont\textcolor{color1}{#1}}} +\renewcommand*{\sectionstyle}[1]{{\sectionfont\textcolor{color1}{#1}}} +\renewcommand*{\subsectionstyle}[1]{{\subsectionfont\textcolor{color1}{#1}}} +\renewcommand*{\hintstyle}[1]{{\hintfont\textcolor{color0}{#1}}} + +% lengths +\newlength{\quotewidth} +\newlength{\hintscolumnwidth} +\setlength{\hintscolumnwidth}{0.175\textwidth} +\newlength{\separatorcolumnwidth} +\setlength{\separatorcolumnwidth}{0.025\textwidth} +\newlength{\maincolumnwidth} +\newlength{\doubleitemmaincolumnwidth} +\newlength{\listitemsymbolwidth} +\settowidth{\listitemsymbolwidth}{\listitemsymbol} +\newlength{\listitemmaincolumnwidth} +\newlength{\listdoubleitemmaincolumnwidth} + +% commands +\renewcommand*{\recomputecvlengths}{% + \setlength{\quotewidth}{0.65\textwidth}% + % main lenghts + \setlength{\maincolumnwidth}{\textwidth-\separatorcolumnwidth-\hintscolumnwidth}% + % listitem lengths + \setlength{\listitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth}% + % doubleitem lengths + \setlength{\doubleitemmaincolumnwidth}{\maincolumnwidth-\hintscolumnwidth-\separatorcolumnwidth-\separatorcolumnwidth}% + \setlength{\doubleitemmaincolumnwidth}{0.5\doubleitemmaincolumnwidth}% + % listdoubleitem lengths + \setlength{\listdoubleitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth-\separatorcolumnwidth-\listitemsymbolwidth}% + \setlength{\listdoubleitemmaincolumnwidth}{0.5\listdoubleitemmaincolumnwidth}% + % fancyhdr lengths + \renewcommand{\headwidth}{\textwidth}% + % regular lengths + \setlength{\parskip}{0\p@}} + +% optional maketitle width to force a certain width (if set to 0pt, the width is calculated automatically) +\newlength{\makecvtitlenamewidth} +\setlength{\makecvtitlenamewidth}{0pt}% dummy value +\renewcommand*{\makecvtitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputecvlengths% + % optional detailed information (pre-rendering) + \def\phonesdetails{}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \protected@edef\phonesdetails{\phonesdetails\protect\makenewline\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}}% + \newbox{\makecvtitledetailsbox}% + \savebox{\makecvtitledetailsbox}{% + \addressfont\color{color2}% + \begin{tabular}[b]{@{}r@{}}% + \ifthenelse{\isundefined{\@addressstreet}}{}{\makenewline\addresssymbol\@addressstreet% + \ifthenelse{\equal{\@addresscity}{}}{}{\makenewline\@addresscity}% if \addresstreet is defined, \addresscity and addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\makenewline\@addresscountry}}% + \phonesdetails% needed to be pre-rendered as loops and tabulars seem to conflict + \ifthenelse{\isundefined{\@email}}{}{\makenewline\emailsymbol\emaillink{\@email}}% + \ifthenelse{\isundefined{\@homepage}}{}{\makenewline\homepagesymbol\httplink{\@homepage}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\makenewline\@extrainfo}% + \end{tabular} + }% + % optional photo (pre-rendering) + \newbox{\makecvtitlepicturebox}% + \savebox{\makecvtitlepicturebox}{% + \ifthenelse{\isundefined{\@photo}}% + {}% + {% + \hspace*{\separatorcolumnwidth}% + \color{color1}% + \setlength{\fboxrule}{\@photoframewidth}% + \ifdim\@photoframewidth=0pt% + \setlength{\fboxsep}{0pt}\fi% + \framebox{\includegraphics[width=\@photowidth]{\@photo}}}}% + % name and title + \newlength{\makecvtitledetailswidth}\settowidth{\makecvtitledetailswidth}{\usebox{\makecvtitledetailsbox}}% + \newlength{\makecvtitlepicturewidth}\settowidth{\makecvtitlepicturewidth}{\usebox{\makecvtitlepicturebox}}% + \ifthenelse{\lengthtest{\makecvtitlenamewidth=0pt}}% check for dummy value (equivalent to \ifdim\makecvtitlenamewidth=0pt) + {\setlength{\makecvtitlenamewidth}{\textwidth-\makecvtitledetailswidth-\makecvtitlepicturewidth}}% + {}% + \begin{minipage}[b]{\makecvtitlenamewidth}% + \namestyle{\@firstname\ \@lastname}% + \ifthenelse{\equal{\@title}{}}{}{\\[1.25em]\titlestyle{\@title}}% + \end{minipage}% + \hfill% + % optional detailed information (rendering) + \llap{\usebox{\makecvtitledetailsbox}}% \llap is used to suppress the width of the box, allowing overlap if the value of makecvtitlenamewidth is forced + % optional photo (rendering) + \usebox{\makecvtitlepicturebox}\\[2.5em]% + % optional quote + \ifthenelse{\isundefined{\@quote}}% + {}% + {{\centering\begin{minipage}{\quotewidth}\centering\quotestyle{\@quote}\end{minipage}\\[2.5em]}}% + \par}% to avoid weird spacing bug at the first section if no blank line is left after \makecvtitle + +\newlength{\baseletterheight} +\settoheight{\baseletterheight}{\sectionstyle{o}} +\setlength{\baseletterheight}{\baseletterheight-0.95ex} +\RenewDocumentCommand{\section}{sm}{% + \par\addvspace{2.5ex}% + \phantomsection{}% reset the anchor for hyperrefs + \addcontentsline{toc}{section}{#2}% + \parbox[t]{\hintscolumnwidth}{\strut\raggedleft\raisebox{\baseletterheight}{\color{color1}\rule{\hintscolumnwidth}{0.95ex}}}% + \hspace{\separatorcolumnwidth}% + \parbox[t]{\maincolumnwidth}{\strut\sectionstyle{#2}}% + \par\nobreak\addvspace{1ex}\@afterheading}% to avoid a pagebreak after the heading + +\RenewDocumentCommand{\subsection}{sm}{% + \par\addvspace{1ex}% + \phantomsection{}% reset the anchor for hyperrefs + \addcontentsline{toc}{subsection}{#2}% + \begin{tabular}{@{}p{\hintscolumnwidth}@{\hspace{\separatorcolumnwidth}}p{\maincolumnwidth}@{}}% + \raggedleft\hintstyle{} &{\strut\subsectionstyle{#2}}% + \end{tabular}% + \par\nobreak\addvspace{0.5ex}\@afterheading}% to avoid a pagebreak after the heading + +\renewcommand*{\cvitem}[3][.25em]{% + \begin{tabular}{@{}p{\hintscolumnwidth}@{\hspace{\separatorcolumnwidth}}p{\maincolumnwidth}@{}}% + \raggedleft\hintstyle{#2} &{#3}% + \end{tabular}% + \par\addvspace{#1}} + +\renewcommand*{\cvdoubleitem}[5][.25em]{% + \cvitem[#1]{#2}{% + \begin{minipage}[t]{\doubleitemmaincolumnwidth}#3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\hintscolumnwidth}\raggedleft\hintstyle{#4}\end{minipage}% + \hspace*{\separatorcolumnwidth}% + \begin{minipage}[t]{\doubleitemmaincolumnwidth}#5\end{minipage}}} + +\renewcommand*{\cvlistitem}[2][.25em]{% + \cvitem[#1]{}{\listitemsymbol\begin{minipage}[t]{\listitemmaincolumnwidth}#2\end{minipage}}} + +\renewcommand*{\cvlistdoubleitem}[3][.25em]{% + \cvitem[#1]{}{\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#2\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \ifthenelse{\equal{#3}{}}% + {}% + {\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#3\end{minipage}}}} + +\renewcommand*{\cventry}[7][.25em]{% + \cvitem[#1]{#2}{% + {\bfseries#3}% + \ifthenelse{\equal{#4}{}}{}{, {\slshape#4}}% + \ifthenelse{\equal{#5}{}}{}{, #5}% + \ifthenelse{\equal{#6}{}}{}{, #6}% + .\strut% + \ifx&% + \else{\newline{}\begin{minipage}[t]{\linewidth}\small#7\end{minipage}}\fi}} + +\newbox{\cvitemwithcommentmainbox} +\newlength{\cvitemwithcommentmainlength} +\newlength{\cvitemwithcommentcommentlength} +\renewcommand*{\cvitemwithcomment}[4][.25em]{% + \savebox{\cvitemwithcommentmainbox}{{\bfseries#3}}% + \setlength{\cvitemwithcommentmainlength}{\widthof{\usebox{\cvitemwithcommentmainbox}}}% + \setlength{\cvitemwithcommentcommentlength}{\maincolumnwidth-\separatorcolumnwidth-\cvitemwithcommentmainlength}% + \cvitem[#1]{#2}{% + \begin{minipage}[t]{\cvitemwithcommentmainlength}\bfseries#3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\cvitemwithcommentcommentlength}\raggedleft\small\itshape#4\end{minipage}}} + +\renewenvironment{thebibliography}[1]% + {% + \bibliographyhead{\refname}% +% \small% + \begin{list}{\bibliographyitemlabel}% + {% + \setlength{\topsep}{0pt}% + \setlength{\labelwidth}{\hintscolumnwidth}% + \setlength{\labelsep}{\separatorcolumnwidth}% + \leftmargin\labelwidth% + \advance\leftmargin\labelsep% + \@openbib@code% + \usecounter{enumiv}% + \let\p@enumiv\@empty% + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \sloppy\clubpenalty4000\widowpenalty4000% +% \sfcode`\.\@m% +% \sfcode `\=1000\relax% + }% + {% + \def\@noitemerr{\@latex@warning{Empty `thebibliography' environment}}% + \end{list}% + } + + +%------------------------------------------------------------------------------- +% letter style definition +%------------------------------------------------------------------------------- +% commands +\renewcommand*{\recomputeletterlengths}{% + \recomputecvlengths% + \setlength{\parskip}{6\p@}} + +\renewcommand*{\makelettertitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputeletterlengths% + % sender contact info + \hfill% + \begin{minipage}{.5\textwidth}% + \raggedleft% + \addressfont\textcolor{color2}{% + {\bfseries\upshape\@firstname~\@lastname}\@firstdetailselementfalse% + \ifthenelse{\isundefined{\@addressstreet}}{}{\makenewline\addresssymbol\@addressstreet% + \ifthenelse{\equal{\@addresscity}{}}{}{\makenewline\@addresscity}% if \addresstreet is defined, \addresscity and addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\makenewline\@addresscountry}}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \makenewline\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}% + \ifthenelse{\isundefined{\@email}}{}{\makenewline\emailsymbol\emaillink{\@email}}% + \ifthenelse{\isundefined{\@homepage}}{}{\makenewline\homepagesymbol\httplink{\@homepage}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\makenewline\@extrainfo}}% + \end{minipage}\\[1em] + % recipient block + \begin{minipage}[t]{.5\textwidth} + \raggedright% + \addressfont% + {\bfseries\upshape\@recipientname}\\% + \@recipientaddress% + \end{minipage} + % date + \hfill% US style +% \\[1em]% UK style + \@date\\[2em]% US informal style: "January 1, 1900"; UK formal style: "01/01/1900" + % opening + \raggedright% + \@opening\\[1.5em]% + % ensure no extra spacing after \makelettertitle due to a possible blank line +% \ignorespacesafterend% not working + \hspace{0pt}\par\vspace{-\baselineskip}\vspace{-\parskip}} + +\renewcommand*{\makeletterclosing}{ + \@closing\\[3em]% + {\bfseries \@firstname~\@lastname}% + \ifthenelse{\isundefined{\@enclosure}}{}{% + \\% + \vfill% + {\color{color2}\itshape\enclname: \@enclosure}}} + + +\endinput + + +%% end of file `moderncvstyleclassic.sty'. diff --git a/users/grfn/resume/moderncvstyleempty.sty b/users/grfn/resume/moderncvstyleempty.sty new file mode 100644 index 000000000000..85932464d1c5 --- /dev/null +++ b/users/grfn/resume/moderncvstyleempty.sty @@ -0,0 +1,34 @@ +%% start of file `moderncvstyleempty.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvstyleempty}[2013/02/09 v1.3.0 modern curriculum vitae scheme: empty] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- + + +%------------------------------------------------------------------------------- +% package options +%------------------------------------------------------------------------------- + + +%------------------------------------------------------------------------------- +% style definition +%------------------------------------------------------------------------------- +% see moderncv.cls for command declarations that needs to be implemented, e.g. \maketitle, \section, \subsections, \cvline, etc + +\endinput + + +%% end of file `moderncvstyleempty.sty'. diff --git a/users/grfn/resume/moderncvstyleoldstyle.sty b/users/grfn/resume/moderncvstyleoldstyle.sty new file mode 100644 index 000000000000..ff732f4e2af5 --- /dev/null +++ b/users/grfn/resume/moderncvstyleoldstyle.sty @@ -0,0 +1,306 @@ +%% start of file `moderncvstyleoldstyle.sty'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +%------------------------------------------------------------------------------- +% identification +%------------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{moderncvstyleoldstyle}[2013/02/09 v1.3.0 modern curriculum vitae and letter style scheme: oldstyle] + + +%------------------------------------------------------------------------------- +% required packages +%------------------------------------------------------------------------------- +% change the layout of the page on the fly, for resume or letter layout +\RequirePackage{changepage} + + +%------------------------------------------------------------------------------- +% overall style definition +%------------------------------------------------------------------------------- +% fonts +%\ifxetexorluatex +% \setmainfont[Numbers={OldStyle,Proportional}, BoldFont={Kurier Bold}, ItalicFont={Kurier Light Italic}, BoldItalicFont={Kurier Bold Italic}]{Kurier Light} +% \setsansfont[Numbers={OldStyle,Proportional}, BoldFont={Kurier Bold}, ItalicFont={Kurier Light Italic}, BoldItalicFont={Kurier Bold Italic}]{Kurier Light} +% \setmathfont{Kurier Light} +% \setmathfont[range=\mathit,\mathsfit]{Kurier Light Italic} +% \setmathfont[range=\mathbfup,\mathbfsfup]{Kurier Bold} +% \setmathfont[range=\mathbfit,\mathbfsfit]{Kurier Bold Italic} +%\else + \IfFileExists{kurier.sty}% + {\RequirePackage[light,math]{kurier}}% + {} +%\fi + +% symbols +\moderncvicons{letters} + + +%------------------------------------------------------------------------------- +% resume style definition +%------------------------------------------------------------------------------- +% fonts +\renewcommand*{\namefont}{\fontsize{34}{36}\mdseries\upshape} +\renewcommand*{\titlefont}{\LARGE\mdseries\slshape} +\renewcommand*{\addressfont}{\small\mdseries} +\renewcommand*{\quotefont}{\large\itshape} +\renewcommand*{\sectionfont}{\Large\bfseries\upshape} +\renewcommand*{\subsectionfont}{\large\bfseries\itshape} +\renewcommand*{\hintfont}{\bfseries} + +% styles +\renewcommand*{\namestyle}[1]{{\namefont\textcolor{color0}{#1}}} +\renewcommand*{\titlestyle}[1]{{\titlefont\textcolor{color2}{#1}}} +\renewcommand*{\addressstyle}[1]{{\addressfont\textcolor{color2}{#1}}} +\renewcommand*{\quotestyle}[1]{{\quotefont\textcolor{color1}{#1}}} +\renewcommand*{\sectionstyle}[1]{{\sectionfont\textcolor{color1}{#1}}} +\renewcommand*{\subsectionstyle}[1]{{\subsectionfont\textcolor{color1}{#1}}} +\renewcommand*{\hintstyle}[1]{{\hintfont\textcolor{color0}{#1}}} + +% lengths +\newlength{\quotewidth} +\newlength{\hintscolumnwidth} +\setlength{\hintscolumnwidth}{0.3\textwidth}% +\newlength{\separatorcolumnwidth} +\setlength{\separatorcolumnwidth}{0.025\textwidth}% +\newlength{\maincolumnwidth} +\newlength{\doubleitemcolumnwidth} +\newlength{\listitemsymbolwidth} +\settowidth{\listitemsymbolwidth}{\listitemsymbol} +\newlength{\listitemmaincolumnwidth} +\newlength{\listdoubleitemmaincolumnwidth} + +% commands +\setlength{\marginparwidth}{0\p@}% +\setlength{\marginparsep}{0\p@} +\renewcommand*{\recomputecvlengths}{% + % regular lengths + \changepage{}{+\marginparwidth+\marginparsep}{}{}{}{}{}{}{}% if a letter was typeset before the resume, \marginparwidth and \marginparsep will be non-zero; otherwise, this has no effect + \setlength{\marginparwidth}{0\p@}% + \setlength{\marginparsep}{0\p@} + \setlength{\parskip}{0\p@}% + % maketitle lengths + \setlength{\quotewidth}{0.65\textwidth}% + % main lenghts + \setlength{\maincolumnwidth}{\textwidth-\hintscolumnwidth-\separatorcolumnwidth}% + % listitem lengths + \setlength{\listitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth}% + % doubleitem lengths + \setlength{\doubleitemcolumnwidth}{\maincolumnwidth-\separatorcolumnwidth}% + \setlength{\doubleitemcolumnwidth}{0.5\doubleitemcolumnwidth}% + % listdoubleitem lengths + \setlength{\listdoubleitemmaincolumnwidth}{\maincolumnwidth-\listitemsymbolwidth-\separatorcolumnwidth-\listitemsymbolwidth}% + \setlength{\listdoubleitemmaincolumnwidth}{0.5\listdoubleitemmaincolumnwidth}% + % fancyhdr lengths + \renewcommand{\headwidth}{\textwidth}} + +\newcommand{\makecvinfo}[1]{% + \newbox{\makecvinfobox}% + \savebox{\makecvinfobox}{\parbox[t]{\hintscolumnwidth}{#1}}% + \newlength{\makecvinfoheight}% + \setlength{\makecvinfoheight}{\totalheightof{\usebox{\makecvinfobox}}}% the total height of the parbox is the sum of its height (\the\ht\makeinfobox) and its depth (\the\dp\makeinfobox); the \totalheightof command is provided by the "calc" package + \usebox{\makecvinfobox}\vspace{-\makecvinfoheight}% + \newlength{\leftcolumnwidth}% + \setlength{\leftcolumnwidth}{\hintscolumnwidth+\separatorcolumnwidth}% + \par\vspace{-\baselineskip}\vspace{-\parskip}\leftskip=\leftcolumnwidth} + +\renewcommand*{\makecvtitle}{ + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputecvlengths% + % optional picture box + \newbox{\makecvtitlepicturebox}% + \savebox{\makecvtitlepicturebox}{% + \ifthenelse{\isundefined{\@photo}}% + {}% + {% + \color{color1}% + \setlength\fboxrule{\@photoframewidth}% + \ifdim\@photoframewidth=0pt% + \setlength{\fboxsep}{0pt}\fi% + \framebox{\includegraphics[width=\@photowidth]{\@photo}}}}% + % name and title + \newlength{\makecvtitlepicturewidth}\settowidth{\makecvtitlepicturewidth}{\usebox{\makecvtitlepicturebox}}% + \newlength{\makecvtitlenamewidth}\setlength{\makecvtitlenamewidth}{\textwidth-\makecvtitlepicturewidth}% + \begin{minipage}[b]{\makecvtitlenamewidth}% + \namestyle{\@firstname\ \@lastname}% + \ifthenelse{\equal{\@title}{}}{}{\\[1.25em]\titlestyle{\@title}}% + \end{minipage}% + % optional photo + \usebox{\makecvtitlepicturebox}\\[2.5em]% + % optional quote + \ifthenelse{\isundefined{\@quote}}% + {}% + {{\centering\begin{minipage}{\quotewidth}\centering\quotestyle{\@quote}\end{minipage}\\[2.5em]}}% + % optional details + \makecvinfo{% + \addressfont\color{color2}% + \ifthenelse{\isundefined{\@addressstreet}}{}{\makenewline\addresssymbol\@addressstreet% + \ifthenelse{\equal{\@addresscity}{}}{}{\makenewline\@addresscity}% if \addresstreet is defined, \addresscity and \addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\makenewline\@addresscountry}}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \makenewline\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}% + \ifthenelse{\isundefined{\@email}}{}{\makenewline\emailsymbol\emaillink{\@email}}% + \ifthenelse{\isundefined{\@homepage}}{}{\makenewline\homepagesymbol\httplink{\@homepage}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\makenewline\@extrainfo}}} + +\RenewDocumentCommand{\section}{sm}{% + \par\addvspace{2.5ex}% + \phantomsection{}% reset the anchor for hyperrefs + \addcontentsline{toc}{section}{#2}% + \strut\sectionstyle{#2}% + \par\nobreak\addvspace{1ex}\@afterheading}% to avoid a pagebreak after the heading + +\RenewDocumentCommand{\subsection}{sm}{% + \par\addvspace{1ex}% + \phantomsection{}% reset the anchor for hyperrefs + \addcontentsline{toc}{subsection}{#2}% + \strut\subsectionstyle{#2}% + \par\nobreak\addvspace{0.5ex}\@afterheading}% to avoid a pagebreak after the heading + +\renewcommand*{\cvitem}[3][.25em]{% + \ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }{#3}% + \par\addvspace{#1}} + +\renewcommand*{\cvdoubleitem}[5][.25em]{% + \begin{minipage}[t]{\doubleitemcolumnwidth}\hintstyle{#2}: #3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\doubleitemcolumnwidth}\ifthenelse{\equal{#4}{}}{}{\hintstyle{#4}: }#5\end{minipage}% + \par\addvspace{#1}} + +\renewcommand*{\cvlistitem}[2][.25em]{% + \cvitem[#1]{}{\listitemsymbol\begin{minipage}[t]{\listitemmaincolumnwidth}#2\end{minipage}}} + +\renewcommand*{\cvlistdoubleitem}[3][.25em]{% + \cvitem[#1]{}{\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#2\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \ifthenelse{\equal{#3}{}}% + {}% + {\listitemsymbol\begin{minipage}[t]{\listdoubleitemmaincolumnwidth}#3\end{minipage}}}} + +\newbox{\cventryyearbox} +\newlength{\cventrytitleboxwidth} +\renewcommand*{\cventry}[7][.25em]{% + \savebox{\cventryyearbox}{% + \hspace*{2\separatorcolumnwidth}% + \hintstyle{#2}}% + \setlength{\cventrytitleboxwidth}{\widthof{\usebox{\cventryyearbox}}}% + \setlength{\cventrytitleboxwidth}{\maincolumnwidth-\cventrytitleboxwidth}% + \begin{minipage}{\maincolumnwidth}% + \parbox[t]{\cventrytitleboxwidth}{% + \strut% + {\bfseries#3}% + \ifthenelse{\equal{#4}{}}{}{, {\slshape#4}}% + \ifthenelse{\equal{#5}{}}{}{, #5}% + \ifthenelse{\equal{#6}{}}{}{, #6}% + .\strut}% + \usebox{\cventryyearbox}% + \end{minipage}% + \ifx&% + \else{% + \newline{}% + \begin{minipage}[t]{\maincolumnwidth}% + \small% + #7% + \end{minipage}}\fi% + \par\addvspace{#1}} + +\newbox{\cvitemwithcommentmainbox} +\newlength{\cvitemwithcommentmainlength} +\newlength{\cvitemwithcommentcommentlength} +\renewcommand*{\cvitemwithcomment}[4][.25em]{% + \savebox{\cvitemwithcommentmainbox}{\ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }#3}% + \setlength{\cvitemwithcommentmainlength}{\widthof{\usebox{\cvitemwithcommentmainbox}}}% + \setlength{\cvitemwithcommentcommentlength}{\maincolumnwidth-\separatorcolumnwidth-\cvitemwithcommentmainlength}% + \begin{minipage}[t]{\cvitemwithcommentmainlength}\ifthenelse{\equal{#2}{}}{}{\hintstyle{#2}: }#3\end{minipage}% + \hfill% fill of \separatorcolumnwidth + \begin{minipage}[t]{\cvitemwithcommentcommentlength}\raggedleft\small\itshape#4\end{minipage}% + \par\addvspace{#1}} + +\renewenvironment{thebibliography}[1]% + {% + \bibliographyhead{\refname}% +% \small% + \begin{list}{\bibliographyitemlabel}% + {% + \setlength{\topsep}{0pt}% + \setlength{\labelwidth}{\hintscolumnwidth}% + \setlength{\labelsep}{\separatorcolumnwidth}% + \leftmargin\labelwidth% + \advance\leftmargin\labelsep% + \@openbib@code% + \usecounter{enumiv}% + \let\p@enumiv\@empty% + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \sloppy\clubpenalty4000\widowpenalty4000% +% \sfcode`\.\@m% +% \sfcode `\=1000\relax% + }% + {% + \def\@noitemerr{\@latex@warning{Empty `thebibliography' environment}}% + \end{list}% + } + + +%------------------------------------------------------------------------------- +% letter style definition +%------------------------------------------------------------------------------- +% commands +%\newlength{\textwidthdelta}% +\renewcommand*{\recomputeletterlengths}{% + \recomputecvlengths% + \setlength{\parskip}{6\p@}% + \leftskip=0pt% +% \setlength{\textwidthdelta}{+\marginparwidth+\marginparsep}% + \setlength{\marginparwidth}{\hintscolumnwidth}% + \setlength{\marginparsep}{2\separatorcolumnwidth}% +% \addtolength{\textwidthdelta}{-\marginparwidth-\marginparsep}% +% \changepage{}{\textwidthdelta}{-\textwidthdelta}{}{}{}{}{}{}%\changepage{<textheight>}{<textwidth>}{<evensidemargin>}{<oddsidemargin>}{<columnsep>}{<topmargin>}{<headheight>}{<headsep>}{<footskip>} + \changepage{}{-\marginparwidth-\marginparsep}{}{}{}{}{}{}{}%\changepage{<textheight>}{<textwidth>}{<evensidemargin>}{<oddsidemargin>}{<columnsep>}{<topmargin>}{<headheight>}{<headsep>}{<footskip>} + } + +\renewcommand*{\makelettertitle}{% + % recompute lengths (in case we are switching from letter to resume, or vice versa) + \recomputeletterlengths% + % recipient block + {\addressfont% + {\bfseries\upshape\@recipientname}\\% + \@recipientaddress}\\[1em]% + % date + \@date\\[2em]% + % opening + \@opening\\[1.5em]% + % sender contact info + \hspace{0pt}% + \marginpar{% + \addressfont\textcolor{color2}{% + {\bfseries\@firstname~\@lastname}\@firstdetailselementfalse% + \ifthenelse{\isundefined{\@addressstreet}}{}{\makenewline\addresssymbol\@addressstreet% + \ifthenelse{\equal{\@addresscity}{}}{}{\makenewline\@addresscity}% if \addresstreet is defined, \addresscity and \addresscountry will always be defined but could be empty + \ifthenelse{\equal{\@addresscountry}{}}{}{\makenewline\@addresscountry}}% + \collectionloop{phones}{% the key holds the phone type (=symbol command prefix), the item holds the number + \makenewline\csname\collectionloopkey phonesymbol\endcsname\collectionloopitem}% + \ifthenelse{\isundefined{\@email}}{}{\makenewline\emailsymbol\emaillink{\@email}}% + \ifthenelse{\isundefined{\@homepage}}{}{\makenewline\homepagesymbol\httplink{\@homepage}}% + \ifthenelse{\isundefined{\@extrainfo}}{}{\makenewline\@extrainfo}}}% + % ensure no extra spacing after \makelettertitle due to a possible blank line +% \ignorespacesafterend% not working + \par\vspace{-\baselineskip}\vspace{-\parskip}} + +\renewcommand*{\makeletterclosing}{ + \@closing\\[3em]% + {\bfseries\@firstname~\@lastname}% + \ifthenelse{\isundefined{\@enclosure}}{}{% + \\% + \vfill% + {\color{color2}\itshape\enclname: \@enclosure}}} + + +\endinput + + +%% end of file `moderncvstyleoldstyle.sty'. diff --git a/users/grfn/resume/picture.png b/users/grfn/resume/picture.png new file mode 100644 index 000000000000..63b21b5320ed --- /dev/null +++ b/users/grfn/resume/picture.png Binary files differdiff --git a/users/grfn/resume/resume.tex b/users/grfn/resume/resume.tex new file mode 100644 index 000000000000..933558d570b6 --- /dev/null +++ b/users/grfn/resume/resume.tex @@ -0,0 +1,212 @@ +%% start of file `template.tex'. +%% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com). +%% Copyright 2014-2020 Griffin Smith (wildgriffin45@gmail.com). +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +\documentclass[10pt,a4paper,sans]{moderncv} % possible options include font size ('10pt', '11pt' and '12pt'), paper size ('a4paper', 'letterpaper', 'a5paper', 'legalpaper', 'executivepaper' and 'landscape') and font family ('sans' and 'roman') + +\usepackage[inline]{enumitem} + + +% moderncv themes +% style options are 'casual' (default), 'classic', 'oldstyle' and 'banking' +\moderncvstyle{casual} +% color options 'blue' (default), 'orange', 'green', 'red', 'purple', 'grey' and 'black' +\moderncvcolor{black} +% to set the default font; use '\sfdefault' for the default sans serif font, +% '\rmdefault' for the default roman one, or any tex font name +%\renewcommand{\familydefault}{\sfdefault} +\nopagenumbers{} + +\usepackage[utf8]{inputenc} + +\usepackage[scale=0.8, margin=0.65in]{geometry} +\setlength{\hintscolumnwidth}{2.6cm} + +\name{Griffin}{Smith} +\title{Software Engineer} +\phone[mobile]{(720) 206-7218} +\email{grfn@gws.fyi} +\homepage{https://www.gws.fyi} +\extrainfo{References available upon request} + +\begin{document} +\makecvtitle{} +\section{Skills} +\cvitem{Clojure}{Extensive experience architecting, deploying, and building +complex web applications in Clojure and Clojurescript, with a focus on +Re-Frame and Reagent.} +\cvitem{Haskell}{Passionate love for pure functional programming as a hobbyist +pursuit, but also practical experience building production systems in Haskell at +scale, and using Haskell's advanced type system extensions where appropriate to +deliver increased ergonomics and safety.} +\cvitem{Nix}{Experience with adopting and teaching nix at scale in a production +stack both for local development dependencies and for configuring and building +production software. Core contributer to a fork of the nix implementation itself +(tvix) aimed at providing increased safety, performance, and flexibility.} +\cvitem{Scala}{Understanding of Scala from the perspective of a functional +programmer rather than a Java programmer. Experience building production +big-data processing systems using Akka, and deep programming with Scala's type +system using Shapeless.} +\cvitem{Unix/Linux}{Experience with administrating highly available distributed +systems. Passion for the Unix philosophy of discrete, composable units of +functionality.} +\cvitem{Ruby}{Experience building both full-stack applications with Ruby on +Rails in addition to smaller microservices and custom frameworks. Deep +understanding of the internals of the Ruby interpreter and object system.} +\cvitem{Javascript}{Experience developing real-time responsive single-page web +applications using React, in addition to significant contributions to the React +open-source community.} +\cvitem{SQL}{Deep understanding of relational databases, including experience +designing the database schema in Postgres for an application with over a decade +of usage, hundreds of gigabytes of data, complex, multi-tiered hierarchical data +structures, as well as experience writing and optimizing large, complex queries +against that database.} + +\subsection{Additional Tools} +\cvitem{}{\footnotesize + \begin{itemize*} + \item Vim + \item Kubernetes + \item Git + \item Puppet + \item AWS + \item Reagent + \item Datomic + \item Elasticsearch + \item Redis + \item DynamoDB + \item Docker + \item JIRA + \item Java + \item QuickCheck (and similar tools) + \item Python + \item Elixir + \end{itemize*} + \newline + \textbf{Novice Level:} + \begin{itemize*} + \item Rust + \item C++ + \item Erlang + \item Prolog + \item Idris + \item Agda + \item Tensorflow + \end{itemize*}} + +\section{Experience} +\subsection{Employment} +\cventry{2019-present}{Engineering Manager}{Urbint}{New York, NY}{} +{\begin{itemize} + \item Lead of the platform team with two direct reports - a senior SRE and + a senior software engineer. + \item Performed user research on developers, project managers, product + managers, and other internal stakeholders to build the roadmap for the + platform team. + \item Built and maintained a system to deploy one-off full stack + application instances from pull requests to enable easier testing. + \item Led a large, multi-project migration between CI systems that resulted + in a decrease of average build times from 2 hours to less than 10 minutes. + \item Maintained and extended Nix-based build and development + infrastructure for both software engineers and machine learning engineers. + \end{itemize}} +\cventry{2018--2019}{Senior Software Engineer}{Urbint}{New York, NY}{} +{\begin{itemize} + \item Built, trained, and maintained a large, deep-learning-based + image-detection model for semi-automated (human-in-the-loop) video + classification. + \item Designed, built, and maintained a novel in-house tool for collection of + training data. + \item Maintained and guaranteed reliability of a large data pipeline for + video processing and classification. + \end{itemize}} +\cventry{2017--2018}{Senior Software Engineer}{Urbint}{New York, NY}{} +{\begin{itemize} + \item Integral in the architecture of a novel, serializable ACID + transactional graph database built on RocksDB, first in Elixir then in + Haskell. + \item Helped ship customer deliverables involving multi-day data + processing jobs for disparate data sources. + \item Instructed other developers in the use of and theory behind Haskell + \item Brought computational graph theory to bear on the problem of unifying + disparate, highly heterogeneous data sources across the world of open data. + \end{itemize}} +\cventry{2016--2017}{Senior Software Engineer}{SecurityScorecard, Inc.}{New York, NY}{} +{Lead frontend developer for a rapidly-moving and growing security software startup. + \begin{itemize} + \item Took part in collaborative product design meetings to make UX + tradeoffs with product designers and managers. + \item Drove application architecture for a large, complex, data-driven frontend + application. + \item Championed increased use of production monitoring and alerting. + \item Worked with business stakeholders to set long- and short-term priorities for + application development. + \item Mentored junior team members. + \end{itemize}} +\cventry{2015--2016}{Lead Developer}{Nomi, Inc.}{New York, NY}{} +{Lead web services developer transitioning to a full-stack role implementing + shared software components and architecting a large, complex microservices + application ingesting hundreds of gigabytes of IoT data per week. + \begin{itemize} + \item Lead application architecture of the majority of the backend services to + encourage consistent REST API design and code sharing. + \item Championed the use of Haskell for rapid, safe development of the API Gateway + service. + \item Took ownership of operations and server maintenance of a >100-instance AWS + account using Puppet. + \end{itemize}} +\cventry{2014--2015}{Lead Developer}{LandlordsNY, LLC}{New York, NY}{} +{Sole engineer for a small startup connecting landlords and property managers and + facilitating the online sharing of information in a historically technology-averse + industry. + \begin{itemize} + \item Drove product design, visual design, and UX architecture for a major revamping + of the core product. + \item Interfaced with customers to set priorities for new feature development. + \item Conducted hiring and recruiting to build out an engineering team. + \end{itemize}} +\cventry{2012--2014}{Associate Developer}{Visionlink Inc.}{Boulder, CO}{} +{Integral member of an agile development team building the nation's most-used Information + and Referral platform for organizations such as United Way Worldwide and the American Red + Cross. + \begin{itemize} + \item Refactored and revamped legacy code to increase performance and long-term + maintainablity. + \item Worked on several triage-teams to rapidly fix production bugs with strict deadlines. + \item Built a complex, yet highly-performant tool for searching human services by category. + \item Acted as a core designer and developer of a major product revamp. + \begin{itemize} + \item Drove a complete rethinking of the data model in the product, leading to greater + unification, simplicity, and consistency; + \item Championed the adoption of a test-driven-development model; + \item Drove product documentation and code standardization. + \end{itemize} + \end{itemize}} + +\section{Project Highlights} +\newcommand{\project}[3]{\item \textbf{#1} -- \textit{#2}\newline{}#3} +\cvitem{}{\begin{itemize} + \project{Github Bug Bounty}{https://bounty.github.com/researchers/glittershark.html}{ + Discovered and responsibly disclosed a persistent XSS on Github's main + website} + \project{Tvix}{https://cs.tvl.fyi/depot/-/blob/third\_party/nix/README.md}{ + Fork of the Nix build tool delivering increased reliability, code + quality, and pluggability} + \project{Panettone}{https://cs.tvl.fyi/depot/-/tree/web/panettone}{ + Aggressively simple bug-tracker developed in Common Lisp for the community + involved in the development of Tvix. Hosted at https://b.tvl.fyi} + \project{Org-Clubhouse}{https://github.com/glittershark/org-clubhouse}{ + Emacs library for integration between org-mode and the Clubhouse issue + tracker} + \project{core-async-storage}{https://github.com/glittershark/core-async-storage}{ + Simple Clojurescript wrapper around React Native's AsyncStorage using + core.async} +\end{itemize}} + +\end{document} +% vim: set tw=95 colorcolumn=-1: diff --git a/users/grfn/resume/tweaklist.sty b/users/grfn/resume/tweaklist.sty new file mode 100644 index 000000000000..adc939893261 --- /dev/null +++ b/users/grfn/resume/tweaklist.sty @@ -0,0 +1,56 @@ +%% start of file `tweaklist.sty'. +%% Original by Jakob Schiรธtz, downloaded from http://dcwww.camd.dtu.dk/~schiotz/comp/LatexTips/tweaklist.sty; not found on ctan. +%% Modified by Xavier Danaux (xdanaux@gmail.com). +% +% The tweaklist.sty package redefines the itemize, enumerate and description packages, so that all parameters can be adjusted. +% This was done by copying the original definitions, and adding "hook commands" that are executed when entering the environment. +% The hook commands are initially empty, but can be redefined with \renewcommand. +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License version 1.3c, +% available at http://www.latex-project.org/lppl/. + + +% hooks for the itemize environment +\def\itemhook{} +\def\itemhooki{} +\def\itemhookii{} +\def\itemhookiii{} +\def\itemhookiv{} +% hooks for the enumerate environment +\def\enumhook{} +\def\enumhooki{} +\def\enumhookii{} +\def\enumhookiii{} +\def\enumhookiv{} +% hook for the description environment +\def\deschook{} +% original environment definitions, with hooks added +\def\enumerate{% + \ifnum \@enumdepth >\thr@@\@toodeep\else + \advance\@enumdepth\@ne + \edef\@enumctr{enum\romannumeral\the\@enumdepth}% + \expandafter + \list + \csname label\@enumctr\endcsname + {% + \enumhook \csname enumhook\romannumeral\the\@enumdepth\endcsname% + \usecounter\@enumctr\def\makelabel##1{\hss\llap{##1}}% + }% + \fi} +\def\itemize{% + \ifnum \@itemdepth >\thr@@\@toodeep\else + \advance\@itemdepth\@ne + \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}% + \expandafter + \list + \csname\@itemitem\endcsname + {% + \itemhook \csname itemhook\romannumeral\the\@itemdepth\endcsname% + \def\makelabel##1{\hss\llap{##1}}% + }% + \fi} +\newenvironment{description} + {\list{}{\deschook\labelwidth\z@ \itemindent-\leftmargin + \let\makelabel\descriptionlabel}} + {\endlist} diff --git a/users/grfn/system/.gitignore b/users/grfn/system/.gitignore new file mode 100644 index 000000000000..41fbeb02c47d --- /dev/null +++ b/users/grfn/system/.gitignore @@ -0,0 +1 @@ +**/result diff --git a/users/grfn/system/home/.skip-subtree b/users/grfn/system/home/.skip-subtree new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/grfn/system/home/.skip-subtree diff --git a/users/grfn/system/home/common/solarized.nix b/users/grfn/system/home/common/solarized.nix new file mode 100644 index 000000000000..e94693edc566 --- /dev/null +++ b/users/grfn/system/home/common/solarized.nix @@ -0,0 +1,18 @@ +rec { + base03 = "#002B36"; + base02 = "#073642"; + base01 = "#586e75"; + base00 = "#657b83"; + base0 = "#839496"; + base1 = "#93a1a1"; + base2 = "#eee8d5"; + base3 = "#fdf6e3"; + yellow = "#b58900"; + orange = "#cb4b16"; + red = "#dc322f"; + magenta = "#d33682"; + violet = "#6c71c4"; + blue = "#268bd2"; + cyan = "#2aa198"; + green = "#859900"; +} diff --git a/users/grfn/system/home/default.nix b/users/grfn/system/home/default.nix new file mode 100644 index 000000000000..f62704152b28 --- /dev/null +++ b/users/grfn/system/home/default.nix @@ -0,0 +1,32 @@ +{ pkgs, depot, lib, ... }: + +with lib; + +rec { + home = confPath: (import "${pkgs.home-manager.src}/modules" { + inherit pkgs; + + configuration = { config, lib, ... }: { + imports = [ confPath ]; + lib.depot = depot; + + # home-manager exposes no API to override the package set that + # is used, unless called from the NixOS module. + # + # To get around it, the module argument is overridden here. + _module.args.pkgs = mkForce pkgs; + }; + }); + + dobharchu = home ./machines/dobharchu.nix; + + dobharchuHome = dobharchu.activation-script; + + yeren = home ./machines/yeren.nix; + + yerenHome = yeren.activation-script; + + meta.targets = [ + "yerenHome" + ]; +} diff --git a/users/grfn/system/home/home.nix b/users/grfn/system/home/home.nix new file mode 100644 index 000000000000..39045c147d76 --- /dev/null +++ b/users/grfn/system/home/home.nix @@ -0,0 +1,20 @@ +{ config, pkgs, ... }: + +{ + imports = [ + (throw "Pick a machine from ./machines") + ]; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; + + # This value determines the Home Manager release that your + # configuration is compatible with. This helps avoid breakage + # when a new Home Manager release introduces backwards + # incompatible changes. + # + # You can update Home Manager without changing this value. See + # the Home Manager release notes for a list of state version + # changes in each release. + home.stateVersion = "19.09"; +} diff --git a/users/grfn/system/home/machines/dobharchu.nix b/users/grfn/system/home/machines/dobharchu.nix new file mode 100644 index 000000000000..0b8503a00e98 --- /dev/null +++ b/users/grfn/system/home/machines/dobharchu.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ../platforms/darwin.nix + ../modules/common.nix + ../modules/games.nix + ]; + + home.packages = with pkgs; [ + coreutils + gnupg + nix-prefetch-github + pass + pinentry_mac + ]; +} diff --git a/users/grfn/system/home/machines/roswell.nix b/users/grfn/system/home/machines/roswell.nix new file mode 100644 index 000000000000..f68f3a7be36f --- /dev/null +++ b/users/grfn/system/home/machines/roswell.nix @@ -0,0 +1,54 @@ +{ pkgs, lib, config, ... }: + +let + laptopKeyboardId = "5"; +in + +{ + imports = [ + ../platforms/linux.nix + ../modules/shell.nix + ../modules/development.nix + ../modules/emacs.nix + ../modules/vim.nix + ]; + + home.packages = with pkgs; [ + # System utilities + bat + htop + killall + bind + zip unzip + tree + ncat + bc + pv + + # Security + gnupg + keybase + openssl + + # Nix things + nixfmt + nix-prefetch-github + nix-review + cachix + ]; + + programs.password-store.enable = true; + + programs.home-manager.enable = true; + home.stateVersion = "20.03"; + + xsession.enable = lib.mkForce false; + + services.lorri.enable = true; + + programs.direnv = { + enable = true; + enableBashIntegration = true; + enableZshIntegration = true; + }; +} diff --git a/users/grfn/system/home/machines/yeren.nix b/users/grfn/system/home/machines/yeren.nix new file mode 100644 index 000000000000..0c89e9d12393 --- /dev/null +++ b/users/grfn/system/home/machines/yeren.nix @@ -0,0 +1,77 @@ +{ pkgs, lib, config, ... }: + +let + inherit (builtins) pathExists; + laptopKeyboardId = "5"; +in + +{ + imports = [ + ../platforms/linux.nix + ../modules/common.nix + ../modules/development/readyset.nix + ] ++ (lib.optional (pathExists ../modules/private.nix) ../modules/private.nix); + + # for when hacking + programs.home-manager.enable = true; + home.stateVersion = "20.03"; + + system.machine = { + wirelessInterface = "wlp0s20f3"; + i3FontSize = 9; + }; + + home.packages = with pkgs; [ + zoom-us + slack + mysql + graphviz + mypaint + xdot + tdesktop + subsurface + + (discord.override rec { + version = "0.0.16"; + src = fetchurl { + url = "https://dl.discordapp.net/apps/linux/${version}/discord-${version}.tar.gz"; + sha256 = "1s9qym58cjm8m8kg3zywvwai2i3adiq6sdayygk2zv72ry74ldai"; + }; + }) + + steam + + awscli2 + ]; + + systemd.user.services.laptop-keyboard = { + Unit = { + Description = "Swap caps+escape and alt+super, but only on the built-in laptop keyboard"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + + Service = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = ( + "${pkgs.xorg.setxkbmap}/bin/setxkbmap " + + "-device ${laptopKeyboardId} " + + "-option caps:swapescape " + + "-option compose:ralt " + + "-option altwin:swap_alt_win" + ); + }; + }; + + xsession.windowManager.i3.config.keybindings.F9 = "exec lock"; + + xdg.mimeApps.defaultApplications."x-scheme-handler/tg" = + "telegramdesktop.desktop"; + + programs.zsh.shellAliases = { + "graph" = "curl -s localhost:6033/graph | dot -Tpng | feh -"; + }; +} diff --git a/users/grfn/system/home/modules/alacritty.nix b/users/grfn/system/home/modules/alacritty.nix new file mode 100644 index 000000000000..67d6638a31f8 --- /dev/null +++ b/users/grfn/system/home/modules/alacritty.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +{ + programs.alacritty = { + enable = true; + settings = { + font.size = 6; + font.normal.family = "Meslo LGSDZ Nerd Font"; + + draw_bold_text_with_bright_colors = false; + + key_bindings = [ + { + key = "Escape"; + mods = "Control"; + action = "ToggleViMode"; + } + ]; + + colors = with import ../common/solarized.nix; rec { + # Default colors + primary = { + background = base3; + foreground = base00; + }; + + cursor = { + text = base3; + cursor = base00; + }; + + # Normal colors + normal = { + inherit red green yellow blue magenta cyan; + black = base02; + white = base2; + }; + + # Bright colors + # bright = normal; + bright = { + black = base03; + red = orange; + green = base01; + yellow = base00; + blue = base0; + magenta = violet; + cyan = base1; + white = base3; + }; + + vi_mode_cursor.cursor = red; + }; + }; + }; +} diff --git a/users/grfn/system/home/modules/alsi.nix b/users/grfn/system/home/modules/alsi.nix new file mode 100644 index 000000000000..c4a14e683c39 --- /dev/null +++ b/users/grfn/system/home/modules/alsi.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: +{ + home.packages = [ config.lib.depot.third_party.alsi ]; + + xdg.configFile."alsi/alsi.logo" = { + source = ./nixos-logo.txt; + force = true; + }; + + xdg.configFile."alsi/alsi.conf" = { + force = true; + text = '' + #!${pkgs.perl}/bin/perl + + scalar { + ALSI_VERSION => "0.4.8", + COLORS_FILE => "/home/grfn/.config/alsi/alsi.colors", + DE_FILE => "/home/grfn/.config/alsi/alsi.de", + DEFAULT_COLOR_BOLD => "blue", + DEFAULT_COLOR_NORMAL => "blue", + DF_COMMAND => "df -Th -x sys -x tmpfs -x devtmpfs &>/dev/stdout", + GTK2_RC_FILE => "/home/grfn/.gtkrc-2.0", + GTK3_RC_FILE => "/home/grfn/.config/gtk-3.0/settings.ini", + LOGO_FILE => "/home/grfn/.config/alsi/alsi.logo", + OUTPUT_FILE => "/home/grfn/.config/alsi/alsi.output", + # PACKAGES_PATH => "/var/lib/pacman/local/", + PS_COMMAND => "ps -A", + USAGE_COLORS => 0, + USAGE_COLORS_BOLD => 0, + USAGE_PRECENT_GREEN => 50, + USAGE_PRECENT_RED => 100, + USAGE_PRECENT_YELLOW => 85, + USE_LOGO_FROM_FILE => 1, + USE_VALUES_COLOR => 0, + WM_FILE => "/home/grfn/.config/alsi/alsi.wm", + } + ''; + }; + + xdg.configFile."alsi/alsi.colors".text = '' + #!${pkgs.perl}/bin/perl + + # Colors for alsi + + scalar { + black => {normal => "\e[0;30m", bold => "\e[1;30m"}, + red => {normal => "\e[0;31m", bold => "\e[1;31m"}, + green => {normal => "\e[0;32m", bold => "\e[1;32m"}, + yellow => {normal => "\e[0;33m", bold => "\e[1;33m"}, + default => {normal => "\e[0;34m", bold => "\e[1;34m"}, + blue => {normal => "\e[0;34m", bold => "\e[1;34m"}, + purple => {normal => "\e[0;35m", bold => "\e[1;35m"}, + cyan => {normal => "\e[0;36m", bold => "\e[1;36m"}, + white => {normal => "\e[0;37m", bold => "\e[1;37m"}, + reset => "\e[0m", + } + ''; +} diff --git a/users/grfn/system/home/modules/common.nix b/users/grfn/system/home/modules/common.nix new file mode 100644 index 000000000000..86e4ba400e7c --- /dev/null +++ b/users/grfn/system/home/modules/common.nix @@ -0,0 +1,105 @@ +{ config, lib, pkgs, ... }: + +# Everything in here needs to work on linux or darwin + +{ + imports = [ + ../modules/shell.nix + ../modules/development.nix + ../modules/emacs.nix + ../modules/vim.nix + ../modules/tarsnap.nix + ../modules/twitter.nix + ../modules/lib/cloneRepo.nix + ]; + + programs.password-store.enable = true; + + grfn.impure.clonedRepos.passwordStore = { + github = "glittershark/pass"; + path = ".local/share/password-store"; + }; + + home.packages = with pkgs; [ + # System utilities + bat + htop + killall + bind + zip unzip + tree + ncat + bc + pv + + # Security + gnupg + keybase + openssl + + # Nix things + nixfmt + nix-prefetch-github + nix-review + cachix + (writeShellScriptBin "rebuild-mugwump" '' + set -eo pipefail + cd ~/code/depot + nix build -f . users.grfn.system.system.mugwumpSystem -o /tmp/mugwump + nix copy -f . users.grfn.system.system.mugwumpSystem \ + --to ssh://mugwump + system=$(readlink -ef /tmp/mugwump) + ssh mugwump sudo nix-env -p /nix/var/nix/profiles/system --set $system + ssh mugwump sudo $system/bin/switch-to-configuration switch + '') + (writeShellScriptBin "rebuild-home" '' + set -eo pipefail + cd ~/code/depot + nix build -f . users.grfn.system.home.$(hostname)Home -o /tmp/home + /tmp/home/activate + '') + ]; + + programs.ssh = { + enable = true; + + matchBlocks = { + "home" = { + host = "home.gws.fyi"; + forwardAgent = true; + }; + + "dobharchu" = { + host = "dobharchu"; + hostname = "172.16.0.4"; + forwardAgent = true; + user = "griffin"; + }; + + "cerberus" = { + host = "cerberus"; + hostname = "172.16.0.3"; + forwardAgent = true; + user = "griffin"; + }; + + "mugwump" = { + host = "mugwump"; + hostname = "172.16.0.5"; + forwardAgent = true; + }; + + "roswell" = { + host = "roswell"; + hostname = "18.223.118.13"; + forwardAgent = true; + }; + }; + }; + + programs.direnv = { + enable = true; + enableBashIntegration = true; + enableZshIntegration = true; + }; +} diff --git a/users/grfn/system/home/modules/development.nix b/users/grfn/system/home/modules/development.nix new file mode 100644 index 000000000000..1a5e912916f8 --- /dev/null +++ b/users/grfn/system/home/modules/development.nix @@ -0,0 +1,212 @@ +{ config, lib, pkgs, ... }: + +let + + clj2nix = pkgs.callPackage (pkgs.fetchFromGitHub { + owner = "hlolli"; + repo = "clj2nix"; + rev = "3ab3480a25e850b35d1f532a5e4e7b3202232383"; + sha256 = "1lry026mlpxp1j563qs13nhxf37i2zpl7lh0lgfdwc44afybqka6"; + }) {}; + + pg-dump-upsert = pkgs.buildGoModule rec { + pname = "pg-dump-upsert"; + version = "165258deaebded5e9b88f7a0acf3a4b7350e7bf4"; + + src = pkgs.fetchFromGitHub { + owner = "tomyl"; + repo = "pg-dump-upsert"; + rev = version; + sha256 = "1an4h8jjbj3r618ykjwk9brii4h9cxjqy47c4c8rivnvhimgf4wm"; + }; + + vendorSha256 = "1a5fx6mrv30cl46kswicd8lf5i5shn1fykchvbnbhdpgxhbz6qi4"; + }; + +in + +with lib; + +{ + imports = [ + ./lib/zshFunctions.nix + ./development/kube.nix + # TODO(grfn): agda build is broken in the nixpkgs checkout + # ./development/agda.nix + ./development/rust.nix + ]; + + home.packages = with pkgs; [ + jq + yq + gron + gitAndTools.hub + gitAndTools.tig + gitAndTools.gh + shellcheck + httpie + entr + gnumake + inetutils + tokei + jsonnet + ngrok + amber + + gdb + lldb + hyperfine + clang-tools_11 + (rr.overrideAttrs (_: rec { + version = "f25671d094edac8059cec56b98d7f10f2c740697"; + src = pkgs.fetchFromGitHub { + owner = "rr-debugger"; + repo = "rr"; + rev = version; + sha256 = "149s4mw8vl8d3nx15sfp62z0izp2dibz99k720j75rjnkwk2bq2z"; + fetchSubmodules = true; + }; + })) + + clj2nix + clojure + leiningen + clj-kondo + + pg-dump-upsert + + nodePackages.prettier + ] ++ optionals (stdenv.isLinux) [ + # TODO(grfn): replace with stable again once the current julia debacle + # is resolved upstream, see https://github.com/NixOS/nixpkgs/pull/121114 + julia_16-bin + valgrind + ]; + + programs.git = { + enable = true; + package = pkgs.gitFull; + userEmail = "root@gws.fyi"; + userName = "Griffin Smith"; + ignores = [ + "*.sw*" + ".classpath" + ".project" + ".settings/" + ".dir-locals.el" + ".stack-work-profiling" + ".projectile" + ]; + extraConfig = { + github.user = "glittershark"; + merge.conflictstyle = "diff3"; + rerere.enabled = "true"; + }; + + delta = { + enable = true; + options = { + syntax-theme = "Solarized (light)"; + hunk-style = "plain"; + commit-style = "box"; + }; + }; + }; + + home.file.".gdbinit".text = '' + set history filename ~/.gdb_history + set history save on + set history size unlimited + set history remove-duplicates unlimited + set history expansion on + ''; + + home.file.".psqlrc".text = '' + \set QUIET 1 + \timing + \set ON_ERROR_ROLLBACK interactive + \set VERBOSITY verbose + \x auto + \set PROMPT1 '%[%033[1m%]%M/%/%R%[%033[0m%]%# ' + \set PROMPT2 '...%# ' + \set HISTFILE ~/.psql_history- :DBNAME + \set HISTCONTROL ignoredups + \pset null [null] + \unset QUIET + ''; + + programs.readline = { + enable = true; + extraConfig = '' + set editing-mode vi + ''; + }; + + programs.zsh = { + shellAliases = { + # Git + "gwip" = "git add . && git commit -am wip"; + "gpr" = "g pull-request"; + "gcl" = "git clone"; + "grs" = "gr --soft"; + "grhh" = "grh HEAD"; + "grh" = "gr --hard"; + "gr" = "git reset"; + "gcb" = "gc -b"; + "gco" = "gc"; + "gcd" = "gc development"; + "gcm" = "gc master"; + "gcc" = "gc canon"; + "gc" = "git checkout"; + "gbg" = "git branch | grep"; + "gba" = "git branch -a"; + "gb" = "git branch"; + "gcv" = "git commit --verbose"; + "gci" = "git commit"; + "gm" = "git merge"; + "gdc" = "gd --cached"; + "gd" = "git diff"; + "gsl" = "git stash list"; + "gss" = "git show stash"; + "gsad" = "git stash drop"; + "gsa" = "git stash"; + "gst" = "gs"; + "gs" = "git status"; + "gg" = "gl --decorate --oneline --graph --date-order --all"; + "gl" = "git log"; + "gf" = "git fetch"; + "gur" = "gu --rebase"; + "gu" = "git pull"; + "gpf" = "gp -f"; + "gpa" = "gp --all"; + "gpu" = "git push -u origin \"$(git symbolic-ref --short HEAD)\""; + "gp" = "git push"; + "ganw" = "git diff -w --no-color | git apply --cached --ignore-whitespace"; + "ga" = "git add"; + "gnp" = "git --no-pager"; + "g" = "git"; + "git" = "hub"; + "grim" = "git fetch && git rebase -i --autostash origin/master"; + "grom" = "git fetch && git rebase --autostash origin/master"; + "groc" = "git fetch && git rebase --autostash origin/canon"; + "grc" = "git rebase --continue"; + "gcan" = "git commit --amend --no-edit"; + "grl" = "git reflog"; + + # Haskell + "crl" = "cabal repl"; + "cr" = "cabal run"; + "cnb" = "cabal new-build"; + "cob" = "cabal old-build"; + "cnr" = "cabal new-run"; + "cor" = "cabal old-run"; + "ho" = "hoogle"; + }; + + functions = { + gdelmerged = '' + git branch --merged | egrep -v 'master' | tr -d '+ ' | xargs git branch -d + ''; + }; + }; +} diff --git a/users/grfn/system/home/modules/development/agda.nix b/users/grfn/system/home/modules/development/agda.nix new file mode 100644 index 000000000000..afd22a306dc9 --- /dev/null +++ b/users/grfn/system/home/modules/development/agda.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +let + agda-categories = with pkgs.agdaPackages; mkDerivation rec { + pname = "agda-categories"; + version = "2128fab"; + src = pkgs.fetchFromGitHub { + owner = "agda"; + repo = "agda-categories"; + rev = version; + sha256 = "08mc20qaz9vp5rhi60rh8wvjkg5aby3bgwwdhfnxha1663qf1q24"; + }; + + buildInputs = [ standard-library ]; + }; + +in + +{ + imports = [ + ../lib/cloneRepo.nix + ]; + + home.packages = with pkgs; [ + (pkgs.agda.withPackages + (p: with p; [ + p.standard-library + + ])) + ]; + + grfn.impure.clonedRepos = { + agda-stdlib = { + github = "agda/agda-stdlib"; + path = "code/agda-stdlib"; + }; + + agda-categories = { + github = "agda/agda-categories"; + path = "code/agda-categories"; + }; + + categories-examples = { + github = "agda/categories-examples"; + path = "code/categories-examples"; + }; + }; + + home.file.".agda/defaults".text = '' + standard-library + ''; + + home.file.".agda/libraries".text = '' + /home/grfn/code/agda-stdlib/standard-library.agda-lib + /home/grfn/code/agda-categories/agda-categories.agda-lib + ''; + +} diff --git a/users/grfn/system/home/modules/development/kube.nix b/users/grfn/system/home/modules/development/kube.nix new file mode 100644 index 000000000000..97ae4760d43b --- /dev/null +++ b/users/grfn/system/home/modules/development/kube.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: +{ + home.packages = with pkgs; [ + kubectl + kubetail + sops + kubie + # pkgs-unstable.argocd # provided by urbos + ]; + + programs.zsh.shellAliases = { + "kc" = "kubectl"; + "kg" = "kc get"; + "kga" = "kc get --all-namespaces"; + "kpd" = "kubectl get pods"; + "kpa" = "kubectl get pods --all-namespaces"; + "klf" = "kubectl logs -f"; + "kdep" = "kubectl get deployments"; + "ked" = "kubectl edit deployment"; + "kpw" = "kubectl get pods -w"; + "kew" = "kubectl get events -w"; + "kdel" = "kubectl delete"; + "knw" = "kubectl get nodes -w"; + "kev" = "kubectl get events --sort-by='.metadata.creationTimestamp'"; + + "arsy" = "argocd app sync --prune"; + }; + + home.file.".kube/kubie.yaml".text = '' + shell: zsh + prompt: + zsh_use_rps1: true + ''; +} diff --git a/users/grfn/system/home/modules/development/readyset.nix b/users/grfn/system/home/modules/development/readyset.nix new file mode 100644 index 000000000000..19f44b770355 --- /dev/null +++ b/users/grfn/system/home/modules/development/readyset.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./rust.nix + ]; + + home.packages = with pkgs; [ + # This goes in $PATH so I can run it from rofi and parent to my WM + (writeShellScriptBin "dotclip" "xclip -out -selection clipboard | dot -Tpng | feh -") + ]; + + programs.zsh.shellAliases = { + "tf" = "terraform"; + }; +} diff --git a/users/grfn/system/home/modules/development/rust.nix b/users/grfn/system/home/modules/development/rust.nix new file mode 100644 index 000000000000..018b338bff2a --- /dev/null +++ b/users/grfn/system/home/modules/development/rust.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: + + +{ + home.packages = with pkgs; [ + rustup + rust-analyzer + cargo-edit + cargo-expand + cargo-flamegraph + cargo-rr + cargo-udeps + cargo-bloat + sccache + evcxr + ]; + + programs.zsh.shellAliases = { + "cg" = "cargo"; + "cb" = "cargo build"; + "ct" = "cargo test"; + "ctw" = "fd -e rs | entr cargo test"; + "cch" = "cargo check"; + }; + + home.file.".cargo/config".text = '' + [build] + rustc-wrapper = "${pkgs.sccache}/bin/sccache" + ''; +} diff --git a/users/grfn/system/home/modules/emacs.nix b/users/grfn/system/home/modules/emacs.nix new file mode 100644 index 000000000000..6cc38bc7ab05 --- /dev/null +++ b/users/grfn/system/home/modules/emacs.nix @@ -0,0 +1,108 @@ +{ pkgs, lib, config, ... }: + +with lib; + +let + # doom-emacs = pkgs.callPackage (builtins.fetchTarball { + # url = https://github.com/vlaci/nix-doom-emacs/archive/master.tar.gz; + # }) { + # doomPrivateDir = ./doom.d; # Directory containing your config.el init.el + # # and packages.el files + # }; + + depot = config.lib.depot; + +in { + imports = [ + ./lib/cloneRepo.nix + ]; + + # home.packages = [ doom-emacs ]; + # home.file.".emacs.d/init.el".text = '' + # (load "default.el") + # ''; + # + + config = mkMerge [ + { + home.packages = with pkgs; [ + # LaTeX (for org export) + (pkgs.texlive.combine { + inherit (pkgs.texlive) + capt-of + collection-fontsrecommended + dvipng + fancyvrb + float + fncychap + framed + mathpartir + needspace + parskip + scheme-basic + semantic + tabulary + titlesec + ulem + upquote + varwidth + wrapfig + ; + }) + + ispell + + ripgrep + coreutils + fd + clang + gnutls + emacsPackages.telega + ]; + + programs.emacs = { + enable = true; + package = pkgs.emacsUnstable; + extraPackages = (epkgs: + (with epkgs; [ + tvlPackages.dottime + tvlPackages.tvl + vterm + telega + ]) + ); + }; + + grfn.impure.clonedRepos = { + orgClubhouse = { + github = "glittershark/org-clubhouse"; + path = "code/org-clubhouse"; + }; + + doomEmacs = { + github = "hlissner/doom-emacs"; + path = ".emacs.d"; + after = ["emacs.d"]; + onClone = "bin/doom install"; + }; + + "emacs.d" = { + github = "glittershark/emacs.d"; + path = ".doom.d"; + after = ["orgClubhouse"]; + }; + }; + + programs.zsh.shellAliases = { + "ec" = "emacsclient"; + }; + } + (mkIf pkgs.stdenv.isLinux { + # Notes + services.syncthing = { + enable = true; + tray = true; + }; + }) + ]; +} diff --git a/users/grfn/system/home/modules/email.nix b/users/grfn/system/home/modules/email.nix new file mode 100644 index 000000000000..0a3e58205ba6 --- /dev/null +++ b/users/grfn/system/home/modules/email.nix @@ -0,0 +1,87 @@ +{ lib, pkgs, config, ... }: + +with lib; + +let + + # from home-manager/modules/services/lieer.nix + escapeUnitName = name: + let + good = upperChars ++ lowerChars ++ stringToCharacters "0123456789-_"; + subst = c: if any (x: x == c) good then c else "-"; + in stringAsChars subst name; + + accounts = { + personal = { + primary = true; + address = "root@gws.fyi"; + aliases = [ "grfn@gws.fyi" ]; + passEntry = "root-gws-msmtp"; + }; + + work = { + address = "griffin@readyset.io"; + passEntry = "readyset/msmtp"; + }; + + }; + +in { + programs.lieer.enable = true; + programs.notmuch.enable = true; + services.lieer.enable = true; + programs.msmtp.enable = true; + + home.packages = with pkgs; [ + mu + msmtp + ]; + + systemd.user.services = mapAttrs' (name: account: { + name = escapeUnitName "lieer-${name}"; + value.Service = { + ExecStart = mkForce "${pkgs.writeShellScript "sync-${name}" '' + ${pkgs.gmailieer}/bin/gmi sync --path ~/mail/${name} + ''}"; + Environment = "NOTMUCH_CONFIG=${config.home.sessionVariables.NOTMUCH_CONFIG}"; + }; + + }) accounts; + + # xdg.configFile."notifymuch/notifymuch.cfg".text = generators.toINI {} { + # notifymuch = { + # query = "is:unread and is:important"; + # mail_client = ""; + # recency_interval_hours = "48"; + # hidden_tags = "inbox unread attachment replied sent encrypted signed"; + # }; + # }; + + accounts.email.maildirBasePath = "mail"; + accounts.email.accounts = mapAttrs (_: params@{ passEntry, ... }: { + realName = "Griffin Smith"; + passwordCommand = "pass ${passEntry}"; + + flavor = "gmail.com"; + + imapnotify = { + enable = true; + boxes = [ "Inbox" ]; + }; + + gpg = { + key = "0F11A989879E8BBBFDC1E23644EF5B5E861C09A7"; + signByDefault = true; + }; + + notmuch.enable = true; + lieer = { + enable = true; + sync = { + enable = true; + frequency = "*:*"; + }; + }; + msmtp.enable = true; + } // builtins.removeAttrs params ["passEntry"]) accounts; +} diff --git a/users/grfn/system/home/modules/firefox.nix b/users/grfn/system/home/modules/firefox.nix new file mode 100644 index 000000000000..c7e78685a5a3 --- /dev/null +++ b/users/grfn/system/home/modules/firefox.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +{ + + xdg.mimeApps = rec { + enable = true; + defaultApplications = { + "text/html" = [ "firefox.desktop" ]; + "x-scheme-handler/http" = [ "firefox.desktop" ]; + "x-scheme-handler/https" = [ "firefox.desktop" ]; + "x-scheme-handler/ftp" = [ "firefox.desktop" ]; + "x-scheme-handler/chrome" = [ "firefox.desktop" ]; + "application/x-extension-htm" = [ "firefox.desktop" ]; + "application/x-extension-html" = [ "firefox.desktop" ]; + "application/x-extension-shtml" = [ "firefox.desktop" ]; + "application/xhtml+xml" = [ "firefox.desktop" ]; + "application/x-extension-xhtml" = [ "firefox.desktop" ]; + "application/x-extension-xht" = [ "firefox.desktop" ]; + }; + associations.added = defaultApplications; + }; +} diff --git a/users/grfn/system/home/modules/games.nix b/users/grfn/system/home/modules/games.nix new file mode 100644 index 000000000000..da54f99e5b39 --- /dev/null +++ b/users/grfn/system/home/modules/games.nix @@ -0,0 +1,59 @@ +{ config, lib, pkgs, ... }: + +with pkgs; +with lib; + +let + + df-orig = dwarf-fortress-packages.dwarf-fortress-original; + + df-full = (dwarf-fortress-packages.dwarf-fortress-full.override { + theme = null; + enableIntro = false; + enableFPS = true; + }); + + init = runCommand "init.txt" {} '' + substitute "${df-orig}/data/init/init.txt" $out \ + --replace "[INTRO:YES]" "[INTRO:NO]" \ + --replace "[VOLUME:255]" "[VOLUME:0]" \ + --replace "[FPS:NO]" "[FPS:YES]" + ''; + + d_init = runCommand "d_init.txt" {} '' + substitute "${df-orig}/data/init/d_init.txt" $out \ + --replace "[AUTOSAVE:NONE]" "[AUTOSAVE:SEASONAL]" \ + --replace "[AUTOSAVE_PAUSE:NO]" "[AUTOSAVE_PAUSE:YES]" \ + --replace "[INITIAL_SAVE:NO]" "[INITIAL_SAVE:YES]" \ + --replace "[EMBARK_WARNING_ALWAYS:NO]" "[EMBARK_WARNING_ALWAYS:YES]" \ + --replace "[VARIED_GROUND_TILES:YES]" "[VARIED_GROUND_TILES:NO]" \ + --replace "[SHOW_FLOW_AMOUNTS:NO]" "[SHOW_FLOW_AMOUNTS:YES]" + ''; + + df = runCommand "dwarf-fortress" {} '' + mkdir -p $out/bin + sed \ + -e '4icp -f ${init} "$DF_DIR/data/init/init.txt"' \ + -e '4icp -f ${d_init} "$DF_DIR/data/init/d_init.txt"' \ + < "${df-full}/bin/dwarf-fortress" >"$out/bin/dwarf-fortress" + + shopt -s extglob + ln -s ${df-full}/bin/!(dwarf-fortress) $out/bin + + chmod +x $out/bin/dwarf-fortress + ''; + +in mkMerge [ + { + home.packages = [ + crawl + xonotic + ]; + } + (mkIf stdenv.isLinux { + home.packages = [ + df + multimc + ]; + }) +] diff --git a/users/grfn/system/home/modules/i3.nix b/users/grfn/system/home/modules/i3.nix new file mode 100644 index 000000000000..df0859b46aef --- /dev/null +++ b/users/grfn/system/home/modules/i3.nix @@ -0,0 +1,366 @@ +{ config, lib, pkgs, ... }: +let + mod = "Mod4"; + solarized = import ../common/solarized.nix; + # TODO pull this out into lib + emacsclient = eval: pkgs.writeShellScript "emacsclient-eval" '' + msg=$(emacsclient --eval '${eval}' 2>&1) + echo "''${msg:1:-1}" + ''; + screenlayout = { + home = pkgs.writeShellScript "screenlayout_home.sh" '' + xrandr \ + --output eDP-1 --mode 1920x1200 --pos 0x2160 --rotate normal \ + --output DP-1 --off \ + --output DP-2 --off \ + --output DP-3 --mode 3840x2160 --pos 0x0 --rotate normal \ + --output DP-4 --off + ''; + }; + + inherit (builtins) map; + inherit (lib) mkMerge range; +in { + options = with lib; { + system.machine.wirelessInterface = mkOption { + description = '' + Name of the primary wireless interface. Used by i3status, etc. + ''; + default = "wlp3s0"; + type = types.str; + }; + + system.machine.i3FontSize = mkOption { + description = "Font size to use in i3 window decorations etc."; + default = 6; + type = types.int; + }; + }; + + config = + let decorationFont = "MesloLGSDZ ${toString config.system.machine.i3FontSize}"; in + { + home.packages = with pkgs; [ + rofi + rofi-pass + python38Packages.py3status + i3lock + i3status + dconf # for gtk + + # Screenshots + maim + + # GIFs + picom + peek + + (pkgs.writeShellScriptBin "lock" '' + playerctl pause + ${pkgs.i3lock}/bin/i3lock -c 222222 + '') + ]; + + xsession.scriptPath = ".xsession"; + + xsession.windowManager.i3 = { + enable = true; + config = { + modifier = mod; + keybindings = + mkMerge ( + (map + (n: { + "${mod}+${toString n}" = + "workspace ${toString n}"; + "${mod}+Shift+${toString n}" = + "move container to workspace ${toString n}"; + }) + (range 0 9)) + ++ [(rec { + "${mod}+h" = "focus left"; + "${mod}+j" = "focus down"; + "${mod}+k" = "focus up"; + "${mod}+l" = "focus right"; + "${mod}+semicolon" = "focus parent"; + + "${mod}+Shift+h" = "move left"; + "${mod}+Shift+j" = "move down"; + "${mod}+Shift+k" = "move up"; + "${mod}+Shift+l" = "move right"; + + "${mod}+Shift+x" = "kill"; + + "${mod}+Return" = "exec alacritty"; + + "${mod}+Shift+s" = "split h"; + "${mod}+Shift+v" = "split v"; + "${mod}+e" = "layout toggle split"; + "${mod}+w" = "layout tabbed"; + "${mod}+s" = "layout stacking"; + + "${mod}+f" = "fullscreen"; + + "${mod}+Shift+r" = "restart"; + + "${mod}+r" = "mode resize"; + + # Marks + "${mod}+Shift+m" = ''exec i3-input -F "mark %s" -l 1 -P 'Mark: ' ''; + "${mod}+m" = ''exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Go to: ' ''; + + # Screenshots + "${mod}+q" = "exec \"maim | xclip -selection clipboard -t image/png\""; + "${mod}+Shift+q" = "exec \"maim -s | xclip -selection clipboard -t image/png\""; + "${mod}+Ctrl+q" = "exec ${pkgs.writeShellScript "peek.sh" '' + ${pkgs.picom}/bin/picom & + picom_pid=$! + ${pkgs.peek}/bin/peek || true + kill -SIGINT $picom_pid + ''}"; + + # Launching applications + "${mod}+u" = "exec ${pkgs.writeShellScript "rofi" '' + rofi \ + -modi 'combi' \ + -combi-modi "window,drun,ssh,run" \ + -font '${decorationFont}' \ + -show combi + ''}"; + + # Passwords + "${mod}+p" = "exec rofi-pass -font '${decorationFont}'"; + + # Media + "XF86AudioPlay" = "exec playerctl play-pause"; + "XF86AudioNext" = "exec playerctl next"; + "XF86AudioPrev" = "exec playerctl previous"; + "XF86AudioRaiseVolume" = "exec pulseaudio-ctl up"; + "XF86AudioLowerVolume" = "exec pulseaudio-ctl down"; + "XF86AudioMute" = "exec pulseaudio-ctl mute"; + + # Lock + Pause = "exec lock"; + + # Brightness + "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl -q s 5%-"; + "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl -q s 5%+"; + + # Sleep/hibernate + # "${mod}+Escape" = "exec systemctl suspend"; + # "${mod}+Shift+Escape" = "exec systemctl hibernate"; + + # Scratch buffer + "${mod}+minus" = "scratchpad show"; + "${mod}+Shift+minus" = "move scratchpad"; + "${mod}+space" = "focus mode_toggle"; + "${mod}+Shift+space" = "floating toggle"; + + # Screen Layout + "${mod}+Shift+t" = "exec xrandr --auto"; + "${mod}+t" = "exec ${screenlayout.home}"; + "${mod}+Ctrl+t" = "exec ${pkgs.writeShellScript "fix_term.sh" '' + xrandr --output eDP-1 --off && ${screenlayout.home} + ''}"; + + # Notifications + "${mod}+Shift+n" = "exec killall -SIGUSR1 .dunst-wrapped"; + "${mod}+n" = "exec killall -SIGUSR2 .dunst-wrapped"; + "Control+space" = "exec ${pkgs.dunst}/bin/dunstctl close"; + "Control+Shift+space" = "exec ${pkgs.dunst}/bin/dunstctl close-all"; + "Control+grave" = "exec ${pkgs.dunst}/bin/dunstctl history-pop"; + "Control+Shift+period" = "exec ${pkgs.dunst}/bin/dunstctl action"; + })]); + + fonts = [ decorationFont ]; + + colors = with solarized; rec { + focused = { + border = base01; + background = base01; + text = base3; + indicator = red; + childBorder = base02; + }; + focusedInactive = focused // { + border = base03; + background = base03; + # text = base1; + }; + unfocused = focusedInactive; + background = base03; + }; + + modes.resize = { + l = "resize shrink width 5 px or 5 ppt"; + k = "resize grow height 5 px or 5 ppt"; + j = "resize shrink height 5 px or 5 ppt"; + h = "resize grow width 5 px or 5 ppt"; + + Return = "mode \"default\""; + }; + + bars = [{ + statusCommand = + let i3status-conf = pkgs.writeText "i3status.conf" '' + general { + output_format = i3bar + colors = true + color_good = "#859900" + + interval = 1 + } + + order += "external_script current_task" + order += "external_script inbox" + order += "spotify" + order += "volume_status" + order += "wireless ${config.system.machine.wirelessInterface}" + # order += "ethernet enp3s0f0" + order += "cpu_usage" + order += "battery 0" + # order += "volume master" + order += "time" + order += "tztime utc" + + mpd { + format = "%artist - %album - %title" + } + + wireless ${config.system.machine.wirelessInterface} { + format_up = "W: (%quality - %essid - %bitrate) %ip" + format_down = "W: -" + } + + ethernet enp3s0f0 { + format_up = "E: %ip" + format_down = "E: -" + } + + battery 0 { + format = "%status %percentage" + path = "/sys/class/power_supply/BAT%d/uevent" + low_threshold = 10 + } + + cpu_usage { + format = "CPU: %usage" + } + + load { + format = "%5min" + } + + time { + format = " %a %h %d โ %I:%M " + } + + spotify { + color_playing = "#fdf6e3" + color_paused = "#93a1a1" + format_stopped = "" + format_down = "" + format = "{title} - {artist} ({album})" + } + + external_script inbox { + script_path = '${emacsclient "(grfn/num-inbox-items-message)"}' + format = 'Inbox: {output}' + cache_timeout = 120 + color = "#93a1a1" + } + + external_script current_task { + script_path = '${emacsclient "(grfn/org-current-clocked-in-task-message)"}' + # format = '{output}' + cache_timeout = 60 + color = "#93a1a1" + } + + tztime utc { + timezone = "UTC" + format = " %Hยท%M " + } + + volume_status { + format = "โ {percentage}" + format_muted = "โ X" + # device = "default" + # mixer_idx = 0 + } + ''; + in "py3status -c ${i3status-conf}"; + fonts = [ decorationFont ]; + position = "top"; + colors = with solarized; rec { + background = base03; + statusline = base3; + separator = base1; + activeWorkspace = { + border = base03; + background = base1; + text = base3; + }; + focusedWorkspace = activeWorkspace; + inactiveWorkspace = activeWorkspace // { + background = base01; + }; + urgentWorkspace = activeWorkspace // { + background = red; + }; + }; + }]; + }; + }; + + services.dunst = { + enable = true; + settings = with solarized; { + global = { + font = "MesloLGSDZ ${toString (config.system.machine.i3FontSize * 1.5)}"; + allow_markup = true; + format = "<b>%s</b>\n%b"; + sort = true; + alignment = "left"; + geometry = "600x15-40+40"; + idle_threshold = 120; + separator_color = "frame"; + separator_height = 1; + word_wrap = true; + padding = 8; + horizontal_padding = 8; + max_icon_size = 45; + }; + + frame = { + width = 0; + color = "#aaaaaa"; + }; + + urgency_low = { + background = base03; + foreground = base3; + timeout = 5; + }; + + urgency_normal = { + background = base02; + foreground = base3; + timeout = 7; + }; + + urgency_critical = { + background = red; + foreground = base3; + timeout = 0; + }; + }; + }; + + gtk = { + enable = true; + iconTheme.name = "Adwaita"; + theme.name = "Adwaita"; + }; + }; +} diff --git a/users/grfn/system/home/modules/lib/cloneRepo.nix b/users/grfn/system/home/modules/lib/cloneRepo.nix new file mode 100644 index 000000000000..dc487dc6bd05 --- /dev/null +++ b/users/grfn/system/home/modules/lib/cloneRepo.nix @@ -0,0 +1,67 @@ +{ lib, config, ... }: +with lib; +{ + options = { + grfn.impure.clonedRepos = mkOption { + description = "Repositories to clone"; + default = {}; + type = with types; loaOf ( + let sm = submodule { + options = { + url = mkOption { + type = nullOr str; + description = "URL of repository to clone"; + default = null; + }; + + github = mkOption { + type = nullOr str; + description = "Github owner/repo of repository to clone"; + default = null; + }; + + path = mkOption { + type = str; + description = "Path to clone to"; + }; + + onClone = mkOption { + type = str; + description = '' + Shell command to run after cloning the repo for the first time. + Runs inside the repo itself. + ''; + default = ""; + }; + + after = mkOption { + type = listOf str; + description = "Activation hooks that this repository must be cloned after"; + default = []; + }; + }; + }; + in addCheck sm (cr: (! isNull cr.url || ! isNull cr.github)) + ); + }; + }; + + config = { + home.activation = + mapAttrs + (_: { + url, path, github, onClone, after, ... + }: + let repoURL = if isNull url then "git@github.com:${github}" else url; + in hm.dag.entryAfter (["writeBoundary"] ++ after) '' + $DRY_RUN_CMD mkdir -p $(dirname "${path}") + if [[ ! -d ${path} ]]; then + $DRY_RUN_CMD git clone "${repoURL}" "${path}" + pushd ${path} + $DRY_RUN_CMD ${onClone} + popd + fi + '') + config.grfn.impure.clonedRepos; + }; +} diff --git a/users/grfn/system/home/modules/lib/zshFunctions.nix b/users/grfn/system/home/modules/lib/zshFunctions.nix new file mode 100644 index 000000000000..7c39b3478cfd --- /dev/null +++ b/users/grfn/system/home/modules/lib/zshFunctions.nix @@ -0,0 +1,21 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + options = { + programs.zsh.functions = mkOption { + description = "An attribute set that maps function names to their source"; + default = {}; + type = with types; attrsOf (either str path); + }; + }; + + config.programs.zsh.initExtra = concatStringsSep "\n" ( + mapAttrsToList (name: funSrc: '' + function ${name}() { + ${funSrc} + } + '') config.programs.zsh.functions + ); +} diff --git a/users/grfn/system/home/modules/nixos-logo.txt b/users/grfn/system/home/modules/nixos-logo.txt new file mode 100644 index 000000000000..d4b16b44f0bf --- /dev/null +++ b/users/grfn/system/home/modules/nixos-logo.txt @@ -0,0 +1,26 @@ + [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m/ + [38;5;m,[38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m/[38;5;m( [38;5;m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;m# [38;5;m.[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;m# + [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m/[38;5;061m/[38;5;m/ [38;5;m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;m. [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m/ + [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m/[38;5;067m([38;5;061m/[38;5;061m/[38;5;m, [38;5;m/[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# + [38;5;m([38;5;067m([38;5;067m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/ [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m( + [38;5;m.[38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m#[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m([38;5;m( + [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m.[38;5;m([38;5;067m([38;5;067m([38;5;m( + [38;5;m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;061m/[38;5;061m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;m( + [38;5;m([38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;074m#[38;5;m# [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( + [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m/[38;5;m([38;5;067m([38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;068m([38;5;m( + [38;5;m*[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m.[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m#[38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( + [38;5;m#[38;5;m#[38;5;m#[38;5;m%[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m([38;5;061m/[38;5;061m/[38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m([38;5;m( +[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m.[38;5;m#[38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( +[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m([38;5;m( [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( + [38;5;m#[38;5;m#[38;5;m#[38;5;m%[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m#[38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m. [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m/[38;5;m([38;5;m([38;5;m([38;5;m( + [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m( [38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/ + [38;5;m.[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/ [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m, + [38;5;m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;m* [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m( [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m/ + [38;5;m.[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;m# [38;5;m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m([38;5;m#[38;5;074m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m, + [38;5;m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m# [38;5;m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m% + [38;5;m.[38;5;m# [38;5;m([38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m/[38;5;061m([38;5;061m/[38;5;061m/[38;5;067m([38;5;m([38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;m% + [38;5;m([38;5;061m/[38;5;061m/[38;5;061m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m( [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m%[38;5;110m%[38;5;110m%[38;5;m( + [38;5;m/[38;5;061m/[38;5;067m([38;5;061m/[38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;m# + [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( [38;5;m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;067m([38;5;067m([38;5;068m( [38;5;m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m/ + [38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;m( [38;5;m/[38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;067m([38;5;068m([38;5;067m([38;5;m( [38;5;m.[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m#[38;5;110m#[38;5;110m#[38;5;m% + [38;5;m([38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;m( [38;5;m([38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;068m([38;5;067m([38;5;068m([38;5;m( [38;5;m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;110m%[38;5;110m#[38;5;m/ diff --git a/users/grfn/system/home/modules/obs.nix b/users/grfn/system/home/modules/obs.nix new file mode 100644 index 000000000000..d1dade477ccc --- /dev/null +++ b/users/grfn/system/home/modules/obs.nix @@ -0,0 +1,66 @@ +{ config, lib, pkgs, ... }: + +with pkgs; + +let + libuiohook = stdenv.mkDerivation rec { + pname = "libuiohook"; + version = "1.1"; + src = fetchFromGitHub { + owner = "kwhat"; + repo = "libuiohook"; + rev = version; + sha256 = "1isfxn3cfrdqq22d3mlz2lzm4asf9gprs7ww2xy9c3j3srk9kd7r"; + }; + + preConfigure = '' + ./bootstrap.sh + ''; + + nativeBuildInputs = [ pkg-config ]; + buildInputs = [ + libtool autoconf automake + x11 + xorg.libXtst + xorg.libXinerama + xorg.libxkbfile + libxkbcommon + ]; + }; + + obs-input-overlay = stdenv.mkDerivation rec { + pname = "obs-input-overlay"; + version = "4.8"; + src = fetchFromGitHub { + owner = "univrsal"; + repo = "input-overlay"; + rev = "v${version}"; + sha256 = "1dklg0dx9ijwyhgwcaqz859rbpaivmqxqvh9w3h4byrh5pnkz8bf"; + fetchSubmodules = true; + }; + + nativeBuildInputs = [ cmake ]; + buildInputs = [ obs-studio libuiohook ]; + + postPatch = '' + sed -i CMakeLists.txt \ + -e '2iinclude(${obs-studio.src}/cmake/Modules/ObsHelpers.cmake)' \ + -e '2ifind_package(LibObs REQUIRED)' + ''; + + cmakeFlags = [ + "-Wno-dev" + ]; + }; +in +{ + home.packages = [ + obs-studio + obs-input-overlay + ]; + + xdg.configFile."obs-studio/plugins/input-overlay/bin/64bit/input-overlay.so".source = + "${obs-input-overlay}/lib/obs-plugins/input-overlay.so"; + xdg.configFile."obs-studio/plugins/input-overlay/data".source = + "${obs-input-overlay}/share/obs/obs-plugins/input-overlay"; +} diff --git a/users/grfn/system/home/modules/ptt.nix b/users/grfn/system/home/modules/ptt.nix new file mode 100644 index 000000000000..436c8f261797 --- /dev/null +++ b/users/grfn/system/home/modules/ptt.nix @@ -0,0 +1,44 @@ +{ config, lib, pkgs, ... }: + +let + + pttKeycode = "152"; + sourceID = "3"; + + mute = pkgs.writeShellScript "mute-mic" '' + xset -r ${pttKeycode} + ${pkgs.pulseaudio}/bin/pactl set-source-mute ${sourceID} 1 + ''; + + unmute = pkgs.writeShellScript "unmute-mic" '' + xset -r ${pttKeycode} + ${pkgs.pulseaudio}/bin/pactl set-source-mute ${sourceID} 0 + ''; + +in + +{ + home.packages = with pkgs; [ + xbindkeys + ]; + + + home.file.".xbindkeysrc.scm".text = '' + (xbindkey '("c:${pttKeycode}") "${unmute}") + (xbindkey '(release "c:${pttKeycode}") "${mute}") + ''; + + systemd.user.services."xbindkeys" = { + Unit = { + Description = "Keybind daemon for push-to-talk"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + + Service = { + ExecStart = "${pkgs.xbindkeys}/bin/xbindkeys -n -v"; + }; + }; +} diff --git a/users/grfn/system/home/modules/pure.zsh-theme b/users/grfn/system/home/modules/pure.zsh-theme new file mode 100755 index 000000000000..b4776e81596d --- /dev/null +++ b/users/grfn/system/home/modules/pure.zsh-theme @@ -0,0 +1,151 @@ +#!/bin/zsh -f +# vim: ft=zsh: +# MIT License +# For my own and others sanity +# git: +# %b => current branch +# %a => current action (rebase/merge) +# prompt: +# %F => color dict +# %f => reset color +# %~ => current path +# %* => time +# %n => username +# %m => shortname host +# %(?..) => prompt conditional - %(condition.true.false) + +# turns seconds into human readable time +# 165392 => 1d 21h 56m 32s +prompt_pure_human_time() { + local tmp=$1 + local days=$(( tmp / 60 / 60 / 24 )) + local hours=$(( tmp / 60 / 60 % 24 )) + local minutes=$(( tmp / 60 % 60 )) + local seconds=$(( tmp % 60 )) + (( $days > 0 )) && echo -n "${days}d " + (( $hours > 0 )) && echo -n "${hours}h " + (( $minutes > 0 )) && echo -n "${minutes}m " + echo "${seconds}s" +} + +is_git_repo() { + command git rev-parse --is-inside-work-tree &>/dev/null + return $? +} + +# fastest possible way to check if repo is dirty +prompt_pure_git_dirty() { + # check if we're in a git repo + is_git_repo || return + # check if it's dirty + [[ "$PURE_GIT_UNTRACKED_DIRTY" == 0 ]] && local umode="-uno" || local umode="-unormal" + command test -n "$(git status --porcelain --ignore-submodules ${umode})" + + (($? == 0)) && echo '*' +} + +prompt_pure_git_wip() { + is_git_repo || return + local subject="$(command git show --pretty=%s --quiet HEAD 2>/dev/null)" + [ "$subject" == 'wip' ] && echo '[WIP]' +} + +# displays the exec time of the last command if set threshold was exceeded +prompt_pure_cmd_exec_time() { + local stop=$EPOCHSECONDS + local start=${cmd_timestamp:-$stop} + integer elapsed=$stop-$start + (($elapsed > ${PURE_CMD_MAX_EXEC_TIME:=5})) && prompt_pure_human_time $elapsed +} + +prompt_pure_preexec() { + cmd_timestamp=$EPOCHSECONDS + + # shows the current dir and executed command in the title when a process is active + print -Pn "\e]0;" + echo -nE "$PWD:t: $2" + print -Pn "\a" +} + +# string length ignoring ansi escapes +prompt_pure_string_length() { + echo ${#${(S%%)1//(\%([KF1]|)\{*\}|\%[Bbkf])}} +} + +prompt_pure_nix_info() { + local packages_info='' + if [[ -z $NIX_SHELL_PACKAGES ]]; then + packages_info='[nix-shell]' + else + packages_info="{ $NIX_SHELL_PACKAGES }" + fi + + case $IN_NIX_SHELL in + 'pure') + echo "$fg_bold[green][nix-shell] " + ;; + 'impure') + echo "$fg_bold[magenta][nix-shell] " + ;; + *) ;; + esac +} + +prompt_pure_precmd() { + # shows the full path in the title + print -Pn '\e]0;%~\a' + + # git info + vcs_info + + local prompt_pure_preprompt="\n$(prompt_pure_nix_info)$fg_bold[green]$prompt_pure_username%F{blue}%~%F{yellow}$vcs_info_msg_0_`prompt_pure_git_dirty` $fg_no_bold[red]`prompt_pure_git_wip`%f %F{yellow}`prompt_pure_cmd_exec_time`%f " + print -P $prompt_pure_preprompt + + # check async if there is anything to pull + # (( ${PURE_GIT_PULL:-1} )) && { + # # check if we're in a git repo + # command git rev-parse --is-inside-work-tree &>/dev/null && + # # make sure working tree is not $HOME + # [[ "$(command git rev-parse --show-toplevel)" != "$HOME" ]] && + # # check check if there is anything to pull + # command git fetch &>/dev/null && + # # check if there is an upstream configured for this branch + # command git rev-parse --abbrev-ref @'{u}' &>/dev/null && { + # local arrows='' + # (( $(command git rev-list --right-only --count HEAD...@'{u}' 2>/dev/null) > 0 )) && arrows='โฃ' + # (( $(command git rev-list --left-only --count HEAD...@'{u}' 2>/dev/null) > 0 )) && arrows+='โก' + # print -Pn "\e7\e[A\e[1G\e[`prompt_pure_string_length $prompt_pure_preprompt`C%F{cyan}${arrows}%f\e8" + # } + # } &! + + # reset value since `preexec` isn't always triggered + unset cmd_timestamp +} + + +prompt_pure_setup() { + # prevent percentage showing up + # if output doesn't end with a newline + export PROMPT_EOL_MARK='' + + prompt_opts=(cr subst percent) + + zmodload zsh/datetime + autoload -Uz add-zsh-hook + autoload -Uz vcs_info + + add-zsh-hook precmd prompt_pure_precmd + add-zsh-hook preexec prompt_pure_preexec + + zstyle ':vcs_info:*' enable git + zstyle ':vcs_info:git*' formats ' %b' + zstyle ':vcs_info:git*' actionformats ' %b|%a' + + # show username@host if logged in through SSH + [[ "$SSH_CONNECTION" != '' ]] && prompt_pure_username='%n@%m ' + + # prompt turns red if the previous command didn't exit with 0 + PROMPT='%(?.%F{green}.%F{red})โฏ%f ' +} + +prompt_pure_setup "$@" diff --git a/users/grfn/system/home/modules/rtlsdr.nix b/users/grfn/system/home/modules/rtlsdr.nix new file mode 100644 index 000000000000..a1c717617a62 --- /dev/null +++ b/users/grfn/system/home/modules/rtlsdr.nix @@ -0,0 +1,21 @@ +{ config, lib, pkgs, ... }: + +let + + nixpkgs-gnuradio = import (pkgs.fetchFromGitHub { + owner = "doronbehar"; + repo = "nixpkgs"; + rev = "712561aa5f10bfe6112a1726a912585612a70d1f"; + sha256 = "04yqflbwjcfl9vlplphpj82csqqz9k6m3nj1ybhwgmsc4by7vivl"; + }) {}; + +in + +{ + home.packages = with pkgs; [ + rtl-sdr + nixpkgs-gnuradio.gnuradio + nixpkgs-gnuradio.gnuradio.plugins.osmosdr + nixpkgs-gnuradio.gqrx + ]; +} diff --git a/users/grfn/system/home/modules/shell.nix b/users/grfn/system/home/modules/shell.nix new file mode 100644 index 000000000000..088fe9238c69 --- /dev/null +++ b/users/grfn/system/home/modules/shell.nix @@ -0,0 +1,184 @@ +{ config, lib, pkgs, ... }: +let + shellAliases = rec { + # NixOS stuff + hms = "home-manager switch"; + nor = "sudo nixos-rebuild switch"; + nrs = nor; + nrb = "sudo nixos-rebuild boot"; + ncg = "nix-collect-garbage"; + vihome = "vim ~/.config/nixpkgs/home.nix && home-manager switch"; + virc = "vim ~/code/system/home/modules/shell.nix && home-manager switch && source ~/.zshrc"; + visystem = "sudo vim /etc/nixos/configuration.nix && sudo nixos-rebuild switch"; + + # Nix + ns = "nix-shell"; + nb = "nix build -f ."; + nbl = "nix build -f . --builders ''"; # nix build local + lwo = "lorri watch --once"; + + # Docker and friends + "dcu" = "docker-compose up"; + "dcud" = "docker-compose up -d"; + "dc" = "docker-compose"; + "dcr" = "docker-compose restart"; + "dclf" = "docker-compose logs -f"; + "dck" = "docker"; + "dockerclean" = "dockercleancontainers && dockercleanimages"; + "dockercleanimages" = "docker images -a --no-trunc | grep none | awk '{print \$$3}' | xargs -L 1 -r docker rmi"; + "dockercleancontainers" = "docker ps -a --no-trunc| grep 'Exit' | awk '{print \$$1}' | xargs -L 1 -r docker rm"; + + # Directories + stck = "dirs -v"; + b= "cd ~1"; + ".." = "cd .."; + "..." = "cd ../.."; + "...." = "cd ../../.."; + "....." = "cd ../../../.."; + + # Aliases from old config + "http" = "http --style solarized"; + "grep" = "grep $GREP_OPTIONS"; + "bak" = "~/bin/backup.sh"; + "xmm" = "xmodmap ~/.Xmodmap"; + "asdflkj" = "asdf"; + "asdf" = "asdfghjkl"; + "asdfghjkl" = "echo \"Having some trouble?\""; + "ift" = "sudo iftop -i wlp3s0"; + "first" = "awk '{print \$$1}'"; + "cmt" = "git log --oneline | fzf-tmux | awk '{print \$$1}'"; + "workmon" = "xrandr --output DP-2 --pos 1440x900 --primary"; + "vi" = "vim"; + "adbdev" = "adb devices"; + "adbcon" = "adb connect $GNEX_IP"; + "mpalb" = "mpc search album"; + "mpart" = "mpc search artist"; + "mps" = "mpc search"; + "mpa" = "mpc add"; + "mpt" = "mpc toggle"; + "mpl" = "mpc playlist"; + "dsstore" = "find . -name '*.DS_Store' -type f -ls -delete"; + "df" = "df -h"; + "fs" = "stat -f '%z bytes'"; + "ll" = "ls -al"; + "la" = "ls -a"; + }; +in { + home.packages = with pkgs; [ + zsh + autojump + ntfy + ]; + + home.sessionVariables = { + EDITOR = "vim"; + LS_COLORS = "no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:"; + BROWSER = "firefox"; + BAT_THEME = "ansi-light"; + }; + + programs.bash = { + enable = true; + inherit shellAliases; + }; + + programs.zsh = { + enable = true; + enableAutosuggestions = true; + autocd = true; + + inherit shellAliases; + + history = rec { + save = 100000; + size = save; + }; + + oh-my-zsh = { + enable = true; + + plugins = [ + "battery" + "colorize" + "command-not-found" + "github" + "gitignore" + "postgres" + "systemd" + "themes" + "vi-mode" + ]; + + custom = "${pkgs.stdenv.mkDerivation { + name = "oh-my-zsh-custom"; + unpackPhase = ":"; + installPhase = '' + mkdir -p $out/themes + mkdir -p $out/custom/plugins + ln -s ${./pure.zsh-theme} $out/themes/pure.zsh-theme + ''; + }}"; + + theme = "pure"; + }; + + plugins = [{ + name = "pure-theme"; + src = pkgs.fetchFromGitHub { + owner = "sindresorhus"; + repo = "pure"; + rev = "0a92b02dd4172f6c64fdc9b81fe6cd4bddb0a23b"; + sha256 = "0l8jqhmmjn7p32hdjnv121xsjnqd2c0plhzgydv2yzrmqgyvx7cc"; + }; + }]; + + initExtraBeforeCompInit = '' + zstyle ':completion:*' completer _complete _ignored _correct _approximate + zstyle ':completion:*' matcher-list \'\' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._- :]=** r:|=**' 'l:|=* r:|=*' + zstyle ':completion:*' max-errors 5 + zstyle ':completion:*' use-cache yes + zstyle ':completion::complete:grunt::options:' expire 1 + zstyle ':completion:*' prompt '%e errors' + # zstyle :compinstall filename '~/.zshrc' + autoload -Uz compinit + ''; + + initExtra = '' + source ${./zshrc} + source ${pkgs.fetchFromGitHub { + owner = "zsh-users"; + repo = "zsh-syntax-highlighting"; + rev = "7678a8a22780141617f809002eeccf054bf8f448"; + sha256 = "0xh4fbd54kvwwpqvabk8lpw7m80phxdzrd75q3y874jw0xx1a9q6"; + }}/zsh-syntax-highlighting.zsh + source ${pkgs.autojump}/share/autojump/autojump.zsh + source ${pkgs.fetchFromGitHub { + owner = "chisui"; + repo = "zsh-nix-shell"; + rev = "a65382a353eaee5a98f068c330947c032a1263bb"; + sha256 = "0l41ac5b7p8yyjvpfp438kw7zl9dblrpd7icjg1v3ig3xy87zv0n"; + }}/nix-shell.plugin.zsh + + eval "$(${pkgs.ntfy}/bin/ntfy shell-integration)" + + export RPS1="" + autoload -U promptinit; promptinit + prompt pure + + if [[ "$TERM" == "dumb" ]]; then + unsetopt zle + unsetopt prompt_cr + unsetopt prompt_subst + unfunction precmd + unfunction preexec + export PS1='$ ' + fi + ''; + }; + + programs.fzf = { + enable = true; + enableBashIntegration = true; + enableZshIntegration = true; + }; +} diff --git a/users/grfn/system/home/modules/tarsnap.nix b/users/grfn/system/home/modules/tarsnap.nix new file mode 100644 index 000000000000..4bff19910f05 --- /dev/null +++ b/users/grfn/system/home/modules/tarsnap.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, ... }: + +{ + home.packages = with pkgs; [ + tarsnap + ]; + + home.file.".tarsnaprc".text = '' + ### Recommended options + + # Tarsnap cache directory + cachedir /home/grfn/.cache/tarsnap + + # Tarsnap key file + keyfile /home/grfn/.private/tarsnap.key + + # Don't archive files which have the nodump flag set. + nodump + + # Print statistics when creating or deleting archives. + print-stats + + # Create a checkpoint once per GB of uploaded data. + checkpoint-bytes 1G + + ### Commonly useful options + + # Use SI prefixes to make numbers printed by --print-stats more readable. + humanize-numbers + + ### Other options, not applicable to most systems + + # Aggressive network behaviour: Use multiple TCP connections when + # writing archives. Use of this option is recommended only in + # cases where TCP congestion control is known to be the limiting + # factor in upload performance. + #aggressive-networking + + # Exclude files and directories matching specified patterns. + # Only one file or directory per command; multiple "exclude" + # commands may be given. + #exclude + + # Include only files and directories matching specified patterns. + # Only one file or directory per command; multiple "include" + # commands may be given. + #include + + # Attempt to reduce tarsnap memory consumption. This option + # will slow down the process of creating archives, but may help + # on systems where the average size of files being backed up is + # less than 1 MB. + #lowmem + + # Try even harder to reduce tarsnap memory consumption. This can + # significantly slow down tarsnap, but reduces its memory usage + # by an additional factor of 2 beyond what the lowmem option does. + #verylowmem + + # Snapshot time. Use this option if you are backing up files + # from a filesystem snapshot rather than from a "live" filesystem. + #snaptime <file> + ''; +} diff --git a/users/grfn/system/home/modules/twitter.nix b/users/grfn/system/home/modules/twitter.nix new file mode 100644 index 000000000000..3cb2e90adc34 --- /dev/null +++ b/users/grfn/system/home/modules/twitter.nix @@ -0,0 +1,23 @@ +{ pkgs, lib, ... }: + +{ + home.packages = with pkgs; [ + t + ]; + + home.sessionVariables = { + TWITTER_WHOAMI = "glittershark1"; + }; + + programs.zsh = { + shellAliases = { + "mytl" = "t tl $TWITTER_WHOAMI"; + }; + + functions = { + favelast = "t fave $(t tl -l $1 | head -n1 | cut -d' ' -f1)"; + rtlast = "t rt $(t tl -l $1 | head -n1 | cut -d' ' -f1)"; + tthread = "t reply $(t tl -l $TWITTER_WHOAMI | head -n1 | cut -d' ' -f1) $@"; + }; + }; +} diff --git a/users/grfn/system/home/modules/vim.nix b/users/grfn/system/home/modules/vim.nix new file mode 100644 index 000000000000..b87cb09ad125 --- /dev/null +++ b/users/grfn/system/home/modules/vim.nix @@ -0,0 +1,48 @@ +{ config, pkgs, ... }: +{ + programs.neovim = { + enable = true; + viAlias = true; + vimAlias = true; + plugins = with pkgs.vimPlugins; [ + ctrlp + deoplete-nvim + syntastic + vim-abolish + vim-airline + vim-airline-themes + vim-bufferline + vim-closetag + # vim-colors-solarized + # solarized + (pkgs.vimUtils.buildVimPlugin { + pname = "vim-colors-solarized"; + version = "git"; + src = pkgs.fetchFromGitHub { + owner = "glittershark"; + repo = "vim-colors-solarized"; + rev = "4857c3221ec3f2693a45855154cb61a2cefb514d"; + sha256 = "0kqp5w14g7adaiinmixm7z3x4w74lv1lcgbqjbirx760f0wivf9y"; + }; + }) + vim-commentary + vim-dispatch + vim-endwise + vim-repeat + vim-fugitive + vim-markdown + vim-nix + vim-rhubarb + vim-sexp + vim-sexp-mappings-for-regular-people + vim-sleuth + vim-startify + vim-surround + vim-unimpaired + vinegar + ]; + extraConfig = '' + source ${./vimrc} + ''; + }; +} diff --git a/users/grfn/system/home/modules/vimrc b/users/grfn/system/home/modules/vimrc new file mode 100644 index 000000000000..3e33b5e2bee7 --- /dev/null +++ b/users/grfn/system/home/modules/vimrc @@ -0,0 +1,1121 @@ +" vim:set fdm=marker fmr={{{,}}} ts=2 sts=2 sw=2 expandtab: + + +" Basic Options {{{ +set nocompatible +set modeline +set modelines=10 +syntax enable +filetype plugin indent on +set ruler +set showcmd +set number +set incsearch +set smartcase +set ignorecase +set scrolloff=10 +set tabstop=4 +set shiftwidth=4 +set softtabstop=4 +set nosmartindent +set expandtab +set noerrorbells visualbell t_vb= +set laststatus=2 +set hidden +let mapleader = ',' +let maplocalleader = '\' +set undofile +" set undodir=~/.vim/undo +set wildignore=*.pyc,*.o,.git +set clipboard=unnamedplus +" set backupdir=$HOME/.vim/backup +" set directory=$HOME/.vim/tmp +set foldmarker={{{,}}} +set colorcolumn=+1 +set concealcursor= +set formatoptions+=j +set wildmenu +set wildmode=longest,list:full +set noincsearch +" }}} + +" GUI options {{{ +set go-=m +set go-=T +set go-=r +set go-=L +set go-=e +set guifont=Meslo\ LG\ S\ DZ\ 9 +" }}} + +" Colors {{{ +" set t_Co=256 + +fu! ReverseBackground() + if &bg=="light" + se bg=dark + else + se bg=light + endif +endf +com! BgToggle call ReverseBackground() +nm <F12> :BgToggle<CR> + +set background=light +colorscheme solarized +" }}} + +" --------------------------------------------------------------------------- + +" CtrlP {{{ +let g:ctrlp_custom_ignore = { + \ 'dir': '(node_modules|target)' + \ } +let g:ctrlp_max_files = 0 +let g:ctrlp_max_depth = 100 +" }}} + +" YouCompleteMe {{{ +let g:ycm_semantic_triggers = { + \ 'c' : ['->', '.'], + \ 'objc' : ['->', '.'], + \ 'ocaml' : ['.', '#'], + \ 'cpp,objcpp' : ['->', '.', '::'], + \ 'perl' : ['->'], + \ 'php' : ['->', '::'], + \ 'cs,java,javascript,d,python,perl6,scala,vb,elixir,go' : ['.'], + \ 'vim' : ['re![_a-zA-Z]+[_\w]*\.'], + \ 'lua' : ['.', ':'], + \ 'erlang' : [':'], + \ 'clojure' : [], + \ 'haskell' : ['re!.*', '.', ' ', '('] + \ } + " \ 'haskell' : ['.', '(', ' '] + " \ 'ruby' : ['.', '::'], + " \ 'clojure' : ['(', '.', '/', '['] +" }}} + +" Neocomplete {{{ +if !has('nvim') + " Use neocomplete. + let g:neocomplete#enable_at_startup = 1 + " Use smartcase. + let g:neocomplete#enable_smart_case = 1 + " Set minimum syntax keyword length. + let g:neocomplete#sources#syntax#min_keyword_length = 3 + let g:neocomplete#lock_buffer_name_pattern = '\*ku\*' + + " Define dictionary. + " let g:neocomplete#sources#dictionary#dictionaries = { + " \ 'default' : '', + " \ 'vimshell' : $HOME.'/.vimshell_hist', + " \ 'scheme' : $HOME.'/.gosh_completions' + " \ } + + " Define keyword. + if !exists('g:neocomplete#keyword_patterns') + let g:neocomplete#keyword_patterns = {} + endif + let g:neocomplete#keyword_patterns['default'] = '\h\w*' + + " Plugin key-mappings. + inoremap <expr><C-g> neocomplete#undo_completion() + inoremap <expr><C-l> neocomplete#complete_common_string() + + " Recommended key-mappings. + " <CR>: close popup and save indent. + inoremap <silent> <CR> <C-r>=<SID>my_cr_function()<CR> + function! s:my_cr_function() + return (pumvisible() ? "\<C-y>" : "" ) . "\<CR>" + " For no inserting <CR> key. + "return pumvisible() ? "\<C-y>" : "\<CR>" + endfunction + " <TAB>: completion. + inoremap <expr><TAB> pumvisible() ? "\<C-n>" : "\<TAB>" + " <C-h>, <BS>: close popup and delete backword char. + inoremap <expr><C-h> neocomplete#smart_close_popup()."\<C-h>" + inoremap <expr><BS> neocomplete#smart_close_popup()."\<C-h>" + " Close popup by <Space>. + "inoremap <expr><Space> pumvisible() ? "\<C-y>" : "\<Space>" + + " AutoComplPop like behavior. + "let g:neocomplete#enable_auto_select = 1 + + " Shell like behavior(not recommended). + "set completeopt+=longest + "let g:neocomplete#enable_auto_select = 1 + "let g:neocomplete#disable_auto_complete = 1 + "inoremap <expr><TAB> pumvisible() ? "\<Down>" : "\<C-x>\<C-u>" + + " Enable omni completion. + " autocmd FileType css setlocal omnifunc=csscomplete#CompleteCSS + " autocmd FileType html,markdown setlocal omnifunc=htmlcomplete#CompleteTags + " autocmd FileType javascript setlocal omnifunc=javascriptcomplete#CompleteJS + " autocmd FileType python setlocal omnifunc=pythoncomplete#Complete + " autocmd FileType xml setlocal omnifunc=xmlcomplete#CompleteTags + + " Enable heavy omni completion. + if !exists('g:neocomplete#sources#omni#input_patterns') + let g:neocomplete#sources#omni#input_patterns = {} + endif +endif +" }}} + +" Deoplete {{{ +if has('nvim') + let g:deoplete#enable_at_startup = 1 + + inoremap <silent> <CR> <C-r>=<SID>my_cr_function()<CR> + function! s:my_cr_function() + return (pumvisible() ? "\<C-y>" : "" ) . "\<CR>" + " For no inserting <CR> key. + "return pumvisible() ? "\<C-y>" : "\<CR>" + endfunction + " <TAB>: completion. + inoremap <expr><TAB> pumvisible() ? "\<C-n>" : "\<TAB>" + inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<TAB>" +endif +" }}} + +" Neovim Terminal mode {{{ +if has('nvim') + tnoremap <Esc> <C-\><C-n> + nnoremap \\ :tabedit term://zsh<CR> + nnoremap q\ :call <SID>OpenRepl()<CR> + + if !exists('g:repl_size') + let g:repl_size=9 + endif + + function! s:OpenRepl() " {{{ + " Check if buffer exists and is open + if exists('s:repl_bufname') && bufexists(s:repl_bufname) && bufwinnr(s:repl_bufname) >=? 0 + " If so, just switch to it + execute bufwinnr(s:repl_bufname) . 'wincmd' 'w' + norm i + return + endif + + if !exists('b:console') + let b:console=$SHELL + endif + + let l:console_cmd = b:console + + execute 'bot' g:repl_size . 'new' + set winfixheight nobuflisted + call termopen(l:console_cmd) + let s:repl_bufname = bufname('%') + norm i + endfunction " }}} +endif +" }}} + +" Tagbar options {{{ +let g:tagbar_autoclose = 1 +let g:tagbar_autofocus = 1 +let g:tagbar_compact = 1 +" }}} + +" delimitMate options {{{ +let g:delimitMate_expand_cr = 1 +" }}} + +" UltiSnips options {{{ +let g:UltiSnipsExpandTrigger = '<c-j>' + "g:UltiSnipsJumpForwardTrigger <c-j> + "g:UltiSnipsJumpBackwardTrigger <c-k> +" }}} + +" VDebug Options {{{ +let g:vdebug_options = {'server': '192.168.56.1'} +" }}} + +" Statusline {{{ +let g:airline_powerline_fonts=1 + +if !exists('g:airline_symbols') + let g:airline_symbols = {} +endif +let g:airline_symbols.space = "\ua0" + +let g:airline#extensions#tagbar#flags = 'f' +let g:airline#extensions#tabline#enabled = 1 +let g:airline#extensions#tabline#show_buffers = 0 +let g:airline#extensions#tabline#show_tabs = 1 +let g:airline#extensions#tabline#tab_min_count = 2 +let g:airline#extensions#tmuxline#enabled = 0 + +let g:tmuxline_theme = 'airline' +let g:tmuxline_preset = 'full' + +"set statusline= +"set statusline+=%2*[%n%H%M%R%W]%*\ " flags and buf no +"set statusline+=%-40f%<\ " path +"set statusline+=%=%40{fugitive#statusline()}\ " Vim status +"set statusline+=%1*%y%*%*\ " file type +"set statusline+=%10((%l,%c)%)\ " line and column +"set statusline+=%P " percentage of file +" }}} + +" Code review mode {{{ +fun! GetFontName() + return substitute(&guifont, '^\(.\{-}\)[0-9]*$', '\1', '') +endfun + +fun! <SID>CodeReviewMode() + let &guifont = GetFontName() . ' 15' +endfun +com! CodeReviewMode call <SID>CodeReviewMode() +" }}} + +" Syntastic {{{ +let g:syntastic_enable_signs = 0 + +" Python {{{ +let g:syntastic_python_checkers = ['flake8'] +let g:syntastic_python_flake8_post_args = "--ignore=E101,E223,E224,E301,E302,E303,E501,E701,W,F401,E111,E261" + +" }}} +" Javascript {{{ +let g:syntastic_javascript_checkers = ['eslint'] +let g:flow#autoclose = 1 +let g:flow#enable = 1 + +" augroup syntastic_javascript_jsx +" autocmd! +" autocmd BufReadPre,BufNewFile *.js +" autocmd BufReadPre,BufNewFile *.jsx +" \ let g:syntastic_javascript_checkers = ['jsxhint'] +" augroup END + +" }}} +" Haml {{{ +let g:syntastic_haml_checkers = ['haml_lint'] + +" }}} +" Html {{{ +let g:syntastic_html_checkers = [] + +" }}} +" Ruby {{{ +let g:syntastic_ruby_checkers = ['rubocop'] +" }}} +" SASS/SCSS {{{ +let g:syntastic_scss_checkers = ['scss_lint'] +" }}} +" Haskell {{{ +" let g:syntastic_haskell_checkers = ['ghc-mod'] +" }}} +" Elixir {{{ +let g:syntastic_elixir_checkers = ['elixir'] +let g:syntastic_enable_elixir_checker = 1 +" }}} +" }}} + +" Bufferline {{{ +let g:bufferline_echo=0 +" }}} + +" Eclim {{{ +let g:EclimCompletionMethod = 'omnifunc' +augroup eclim + au! + au FileType java call <SID>JavaSetup() + au FileType java set textwidth=120 +augroup END + +function! s:JavaSetup() abort + noremap <C-I> :JavaImport<CR> + nnoremap K :JavaDocPreview<CR> + nnoremap ]d :JavaSearchContext<CR> + nnoremap [d :JavaSearchContext<CR> + nnoremap g<CR> :JUnit<CR> + nnoremap g\ :Mvn test<CR> +endfunction +" }}} + +" Signify options {{{ +let g:signify_mapping_next_hunk = ']h' +let g:signify_mapping_prev_hunk = '[h' +let g:signify_vcs_list = ['git'] +let g:signify_sign_change = '~' +let g:signify_sign_delete = '-' +" }}} + +" Simplenote {{{ +let g:SimplenoteFiletype = 'markdown' +let g:SimplenoteSortOrder = 'pinned,modifydate,tagged,createdate' +let g:SimplenoteVertical = 1 + +nnoremap <Leader>nn :Simplenote -n<CR> +nnoremap <Leader>nl :Simplenote -l<CR> +nnoremap <Leader>nw :Simplenote -l work<CR> +nnoremap <Leader>nt :Simplenote -t<CR> +" }}} + +" Emmet {{{ +" Expand abbreviation +let g:user_emmet_leader_key = '<C-y>' +" }}} + +" Startify {{{ +let g:startify_bookmarks=[ '~/.vimrc', '~/.zshrc' ] +" }}} + +" Abolish {{{ +let g:abolish_save_file = expand('~/.vim/after/plugin/abolish.vim') +" }}} + +" Rails projections {{{ + +if !exists('g:rails_projections') + let g:rails_projections = {} +endif + +call extend(g:rails_projections, { + \ "config/routes.rb": { "command": "routes" }, + \ "config/structure.sql": { "command": "structure" } + \ }, 'keep') + +if !exists('g:rails_gem_projections') + let g:rails_gem_projections = {} +endif + +call extend(g:rails_gem_projections, { + \ "active_model_serializers": { + \ "app/serializers/*_serializer.rb": { + \ "command": "serializer", + \ "template": "class %SSerializer < ActiveModel::Serializer\nend", + \ "affinity": "model"}}, + \ "react-rails": { + \ "app/assets/javascripts/components/*.jsx": { + \ "command": "component", + \ "template": "var %S = window.%S = React.createClass({\n render: function() {\n }\n});", + \ "alternate": "spec/javascripts/components/%s_spec.jsx" }, + \ "spec/javascripts/components/*_spec.jsx": { + \ "alternate": "app/assets/javascripts/components/{}.jsx" }}, + \ "rspec": { + \ "spec/**/support/*.rb": { + \ "command": "support"}}, + \ "cucumber": { + \ "features/*.feature": { + \ "command": "feature", + \ "template": "Feature: %h"}, + \ "features/support/*.rb": { + \ "command": "support"}, + \ "features/support/env.rb": { + \ "command": "support"}, + \ "features/step_definitions/*_steps.rb": { + \ "command": "steps"}}, + \ "carrierwave": { + \ "app/uploaders/*_uploader.rb": { + \ "command": "uploader", + \ "template": "class %SUploader < CarrierWave::Uploader::Base\nend"}}, + \ "draper": { + \ "app/decorators/*_decorator.rb": { + \ "command": "decorator", + \ "affinity": "model", + \ "template": "class %SDecorator < Draper::Decorator\nend"}}, + \ "fabrication": { + \ "spec/fabricators/*_fabricator.rb": { + \ "command": ["fabricator", "factory"], + \ "alternate": "app/models/%s.rb", + \ "related": "db/schema.rb#%p", + \ "test": "spec/models/%s_spec.rb", + \ "template": "Fabricator :%s do\nend", + \ "affinity": "model"}}, + \ "factory_girl": { + \ "spec/factories/*.rb": { + \ "command": "factory", + \ "alternate": "app/models/%i.rb", + \ "related": "db/structure.sql#%s", + \ "test": "spec/models/%s_spec.rb", + \ "template": "FactoryGirl.define do\n factory :%i do\n end\nend", + \ "affinity": "model"}, + \ "spec/factories.rb": { + \ "command": "factory"}, + \ "test/factories.rb": { + \ "command": "factory"}} + \ }, 'keep') +" }}} + +" Other projections {{{ +let g:projectionist_heuristics = { + \ "config.ru&docker-compose.yml&app/&config/&OWNERS": { + \ "app/jobs/*.rb": { + \ "type": "job", + \ "alternate": "spec/jobs/{}_spec.rb" + \ }, + \ "app/models/*.rb": { + \ "type": "model", + \ "alternate": "spec/models/{}_spec.rb" + \ }, + \ "app/resources/*_resource.rb": { + \ "type": "resource", + \ "alternate": "spec/resources/{}_resource_spec.rb" + \ }, + \ "config/*.yml": { + \ "type": "config" + \ }, + \ "spec/*_spec.rb": { + \ "type": "spec", + \ "alternate": "app/{}.rb" + \ }, + \ "spec/factories/*.rb": { + \ "type": "factory", + \ } + \ }, + \ "svc-gateway.cabal": { + \ "src/*.hs": { + \ "type": "src", + \ "alternate": "test/{}Spec.hs" + \ }, + \ "test/*Spec.hs": { + \ "type": "spec", + \ "alternate": "src/{}.hs", + \ "template": [ + \ "module Gateway.Resource.HierarchySpec (main, spec) where", + \ "", + \ "import Prelude", + \ "import Test.Hspec", + \ "import Data.Aeson", + \ "", + \ "import Gateway.Resource.Hierarchy", + \ "", + \ "main :: IO ()", + \ "main = hspec spec", + \ "", + \ "spec :: Spec", + \ "spec = do", + \ " describe \"something\" $ undefined" + \ ] + \ }, + \ "svc-gateway.cabal": { + \ "type": "cabal" + \ } + \ }, + \ "package.json&.flowconfig": { + \ "src/*.*": { + \ "type": "src", + \ "alternate": "test/{}_spec.js" + \ } + \ }, + \ "pom.xml&src/main/clj/|src/main/cljs": { + \ "*": { + \ "start": "USE_NREPL=1 bin/run -m elephant.dev-system" , + \ "connect": "nrepl://localhost:5554", + \ "piggieback": "(figwheel-sidecar.repl-api/repl-env)" + \ }, + \ "pom.xml": { "type": "pom" }, + \ "src/main/clj/*.clj": { + \ "alternate": "src/test/clj/{}_test.clj", + \ "template": ["(ns {dot|hyphenate})"] + \ }, + \ "src/test/clj/*_test.clj": { + \ "alternate": "src/main/clj/{}.clj", + \ "dispatch": ":RunTests {dot|hyphenate}-test", + \ "template": ["(ns {dot|hyphenate}-test", + \ " (:require [clojure.test :refer :all]))"] + \ }, + \ "src/main/cljs/*.cljs": { + \ "alternate": "src/test/cljs/{}_test.cljs" + \ }, + \ "src/main/cljs/*_test.cljs": { + \ "alternate": "src/main/cljs/{}.cljs", + \ "dispatch": ":RunTests {dot|hyphenate}-test" + \ }, + \ "src/main/clj/*.cljc": { + \ "alternate": "src/test/clj/{}_test.cljc" + \ }, + \ "src/main/clj/*_test.cljc": { + \ "alternate": "src/test/clj/{}.cljc", + \ "dispatch": ":RunTests {dot|hyphenate}-test" + \ } + \ }} +" }}} + +" AutoPairs {{{ +let g:AutoPairsCenterLine = 0 +" }}} + +" Filetypes {{{ + +" Python {{{ +aug Python + au! + au FileType python set tabstop=4 shiftwidth=4 softtabstop=4 expandtab +aug END +let g:python_highlight_all=1 +" }}} + +" PHP {{{ +aug PHP + au! + "au FileType php setlocal fdm=marker fmr={{{,}}} +aug END " }}} + +" Mail {{{ +aug Mail + au FileType mail setlocal spell +aug END " }}} + +" Haskell {{{ +let g:haskell_conceal_wide = 1 +let g:haskellmode_completion_ghc = 0 +let g:necoghc_enable_detailed_browse = 1 + +augroup Haskell + autocmd! + autocmd FileType haskell setlocal textwidth=110 shiftwidth=2 + autocmd FileType haskell setlocal omnifunc=necoghc#omnifunc + autocmd FileType haskell call <SID>HaskellSetup() + autocmd FileType haskell setlocal keywordprg=hoogle\ -cie +augroup END + +function! s:HaskellSetup() + set sw=4 + " compiler cabal + " let b:start='cabal run' + " let b:console='cabal repl' + " let b:dispatch='cabal test' + compiler stack + let b:start='stack run' + let b:console='stack ghci' + let b:dispatch='stack test' + nnoremap <buffer> gy :HdevtoolsType<CR> + nnoremap <buffer> yu :HdevtoolsClear<CR> +endfunction +" }}} + +" Ruby {{{ + +function! s:RSpecSyntax() + syn keyword rspecMethod describe context it its specify shared_context + \ shared_examples shared_examples_for shared_context include_examples + \ include_context it_should_behave_like it_behaves_like before after + \ around fixtures controller_name helper_name scenario feature + \ background given described_class + syn match rspecMethod '\<let\>!\=' + syn match rspecMethod '\<subject\>!\=' + syn keyword rspecMethod violated pending expect expect_any_instance_of allow + \ allow_any_instance_of double instance_double mock mock_model + \ stub_model xit + syn match rspecMethod '\.\@<!\<stub\>!\@!' + + call s:RSpecHiDefaults() +endfunction + +function! s:RSpecHiDefaults() + hi def link rspecMethod rubyFunction +endfunction + +augroup Ruby + au! + " au FileType ruby let b:surround_114 = "\\(module|class,def,if,unless,case,while,until,begin,do) \r end" + " au FileType ruby set fdm=syntax + au FileType ruby set tw=110 + au FileType ruby set omnifunc= + au FileType ruby nnoremap <buffer> gy orequire 'pry'; binding.pry<ESC>^ + au FileType ruby nnoremap <buffer> gY Orequire 'pry'; binding.pry<ESC>^ + au FileType ruby nnoremap <buffer> yu :g/require 'pry'; binding.pry/d<CR> + au BufNewFile,BufRead *_spec.rb call <SID>RSpecSyntax() +augroup END + +let ruby_operators = 1 +let ruby_space_errors = 1 + +let g:rubycomplete_rails = 1 +command! -range ConvertHashSyntax <line1>,<line2>s/:(\S{-})(\s{-})=> /\1:\2/ +" }}} + +" Clojure {{{ + +aug Clojure + au! + autocmd FileType clojure nnoremap <C-S> :Slamhound<CR> + autocmd FileType clojure nnoremap <silent> gr :w <bar> Require <bar> e<CR> + let g:clojure_align_multiline_strings = 1 + let g:clojure_fuzzy_indent_patterns = + \ ['^with', '^def', '^let', '^fact'] + let g:clojure_special_indent_words = + \ 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn,html' + + autocmd FileType clojure setlocal textwidth=80 + autocmd FileType clojure setlocal lispwords+=GET,POST,PATCH,PUT,DELETE | + \ setlocal lispwords+=context,select + autocmd BufNewFile,BufReadPost *.cljx setfiletype clojure + autocmd BufNewFile,BufReadPost *.cljx setlocal omnifunc= + autocmd BufNewFile,BufReadPost *.cljs setlocal omnifunc= + autocmd FileType clojure call <SID>TangentInit() + autocmd FileType clojure call <SID>sexp_mappings() + autocmd BufRead *.cljc ClojureHighlightReferences + autocmd FileType clojure let b:AutoPairs = { + \ '"': '"', + \ '{': '}', + \ '(': ')', + \ '[': ']'} + " Don't auto-pair quote reader macros + " \'`': '`', + " \ '''': '''', + + autocmd User ProjectionistActivate call s:projectionist_connect() + + function! s:projectionist_connect() abort + let connected = !empty(fireplace#path()) + if !connected + for [root, value] in projectionist#query('connect') + try + silent execute "FireplaceConnect" value root + let connected = 1 + break + catch /.*Connection refused.*/ + endtry + endfor + endif + + " if connected && exists(':Piggieback') + " for [root, value] in projectionist#query('piggieback') + " silent execute "Piggieback" value + " break + " endfor + " endif + endfunction + + " autocmd BufNewFile,BufReadPost *.cljx setlocal omnifunc= + " autocmd BufNewFile,BufReadPost *.cljs setlocal omnifunc= + + autocmd FileType clojure let b:console='lein repl' + autocmd FileType clojure call <SID>ClojureMaps() + + function! s:ClojureMaps() abort + nnoremap <silent> <buffer> [m :call search('^(def', 'Wzb')<CR> + nnoremap <silent> <buffer> ]m :call search('^(def', 'Wz')<CR> + endfunction + + command! Scratch call <SID>OpenScratch() + autocmd FileType clojure nnoremap <buffer> \s :Scratch<CR> + + let g:scratch_buffer_name = 'SCRATCH' + + function! s:OpenScratch() + if bufwinnr(g:scratch_buffer_name) > 0 + execute bufwinnr(g:scratch_buffer_name) . 'wincmd' 'w' + return + endif + + vsplit SCRATCH + set buftype=nofile + set filetype=clojure + let b:scratch = 1 + endfunction +aug END + +function! s:sexp_mappings() abort + if !exists('g:sexp_loaded') + return + endif + + nmap <buffer> cfo <Plug>(sexp_raise_list) + nmap <buffer> cfO <Plug>(sexp_raise_element) + nmap <buffer> cfe <Plug>(sexp_raise_element) +endfunction + +function! s:TangentInit() abort + set textwidth=80 + command! TReset call fireplace#session_eval('(user/reset)') + command! TGo call fireplace#session_eval('(user/go)') + command! TMigrate call fireplace#session_eval('(user/migrate)') + command! TRollback call fireplace#session_eval('(user/rollback)') + nnoremap g\ :TReset<CR> +endfunction + +" }}} + +" Go {{{ + +let g:go_highlight_functions = 1 +let g:go_highlight_methods = 1 +let g:go_highlight_structs = 1 +let g:go_highlight_operators = 1 +let g:go_highlight_build_constraints = 1 + +augroup Go + autocmd! + autocmd FileType go setlocal omnifunc=go#complete#Complete + autocmd FileType go setlocal foldmethod=syntax + autocmd FileType go setlocal foldlevel=100 + autocmd FileType go nnoremap <buffer> <F9> :GoTest<CR> + autocmd FileType go inoremap <buffer> <F9> <ESC>:GoTest<CR>i +augroup END + +" }}} + +" RAML {{{ + +function! s:buffer_syntax() " {{{ + syn keyword ramlRAML RAML contained + syn match ramlVersionString '^#%RAML \d\.\d' contains=ramlRAML +endfunction " }}} + +augroup RAML + autocmd! + autocmd BufRead,BufNewFile *.raml set filetype=yaml + autocmd BufRead,BufNewFile *.raml call s:buffer_syntax() +augroup END + +hi def link ramlVersionString Special +hi def link ramlRAML Error +" }}} + +" Mustache/Handlebars {{{ +let g:mustache_abbreviations = 1 +" }}} + +" Netrw {{{ +augroup netrw + autocmd! + autocmd FileType netrw nnoremap <buffer> Q :Rexplore<CR> + + " Hee hee, oil and vinegar + function! s:setup_oil() abort + nnoremap <buffer> q <C-6> + xnoremap <buffer> q <C-6> + endfunction +augroup END +" }}} +" }}} + +" Remove trailing whitespace {{{ +fun! <SID>StripTrailingWhitespaces() + let l = line(".") + let c = col(".") + %s/\s\+$//e + call cursor(l, c) +endfun + +augroup striptrailingwhitespaces " {{{ +autocmd FileType c,cpp,java,php,ruby,python,sql,javascript,sh,jst,less,haskell,haml,coffee,scss,clojure,objc,elixir,yaml,json,eruby + \ autocmd BufWritePre <buffer> :call <SID>StripTrailingWhitespaces() +augroup END " }}} + +" }}} + +" Goyo {{{ +let g:limelight_conceal_ctermfg = "10" +let g:limelight_conceal_guifg = "#586e75" +autocmd! User GoyoEnter Limelight +autocmd! User GoyoLeave Limelight! +" }}} + +"----------------------------------------------------------------------------- + +" Commands {{{ + +" Edit temporary SQL files {{{ +let s:curr_sql = 0 +fun! <SID>EditSqlTempFile() + let l:fname = '/tmp/q' . s:curr_sql . '.sql' + execute 'edit' l:fname + let s:curr_sql = s:curr_sql + 1 +endfun +com! EditSqlTempFile call <SID>EditSqlTempFile() +" }}} + +" Double Indentation +command! -range DoubleIndentation <line1>,<line2>s/^\(\s.\{-}\)\(\S\)/\1\1\2/ + +" Quick-and-dirty fix capitalization of sql files +command! -range FixSqlCapitalization <line1>,<line2>v/\v(^\s*--.*$)|(TG_)/norm guu + +" VimPipe Commands {{{ +" let g:sql_type_default = 'pgsql' +command! SqlLive let b:vimpipe_command="vagrant ssh -c '~/mysql'" +command! SqlRails let b:vimpipe_command="bin/rails dbconsole" +command! SqlHeroku let b:vimpipe_command="heroku pg:psql" +command! SqlEntities let b:vimpipe_command="psql -h 127.1 entities nomi" +command! SqlUsers let b:vimpipe_command="psql -h 127.1 users nomi" +command! SqlTangent let b:vimpipe_command="psql -h local.docker tangent super" +" }}} + +" Git commands {{{ +command! -nargs=* Gpf Gpush -f <args> +command! -nargs=* Gcv Gcommit --verbose <args> +" }}} + +" Focus dispatch to only the last failures +command! -nargs=* FocusFailures FocusDispatch rspec --only-failures <args> + +" }}} + +" Autocommands {{{ + +augroup fugitive " {{{ + au! + autocmd BufNewFile,BufRead fugitive://* set bufhidden=delete +augroup END " }}} + +augroup omni " {{{ + au! + " autocmd FileType javascript setlocal omnifunc=tern#Complete + "autocmd FileType python setlocal omnifunc=pythoncomplete#Complete + autocmd FileType php setlocal omnifunc= +augroup END " }}} + +augroup sql " {{{ + au! + autocmd FileType sql let b:vimpipe_command="psql -h 127.0.0.1 landlordsny_development landlordsny" + autocmd FileType sql let b:vimpipe_filetype="postgresql" + autocmd FileType sql set syntax=postgresql + autocmd FileType postgresql set nowrap + autocmd BufNewFile,BufReadPost *.sql set syntax=pgsql +augroup END " }}} + +augroup markdown " {{{ + au! + autocmd FileType markdown let b:vimpipe_command='markdown' + autocmd FileType markdown let b:vimpipe_filetype='html' + autocmd FileType markdown set tw=80 +augroup END " }}} + +augroup typescript " {{{ + au! + autocmd FileType typescript let b:vimpipe_command='tsc' + autocmd FileType typescript let b:vimpipe_filetype='javascript' + autocmd FileType typescript TSSstarthere + autocmd FileType typescript nnoremap <buffer> gd :TSSdef<CR> +augroup END " }}} + +augroup jsx " {{{ + au! + " autocmd FileType jsx set syntax=javascript + autocmd FileType javascript set filetype=javascript.jsx +augroup END " }}} + +augroup nicefoldmethod " {{{ + au! + " Don't screw up folds when inserting text that might affect them, until + " leaving insert mode. Foldmethod is local to the window. Protect against + " screwing up folding when switching between windows. + autocmd InsertEnter * + \ if !exists('w:last_fdm') | + \ let w:last_fdm=&foldmethod | + \ setlocal foldmethod=manual | + \ endif + autocmd InsertLeave,WinLeave * + \ if exists('w:last_fdm') | + \ let &l:foldmethod=w:last_fdm | + \ unlet w:last_fdm | + \ endif +augroup END " }}} + +augroup visualbell " {{{ + au! + autocmd GUIEnter * set visualbell t_vb= +augroup END +" }}} + +augroup quickfix " {{{ + au! + autocmd QuickFixCmdPost grep cwindow +augroup END " }}} + +augroup php " {{{ + au! +augroup END "}}} + +augroup rubylang " {{{ + au! + autocmd FileType ruby compiler rake +augroup END " }}} + +augroup javascript "{{{ + au! + autocmd FileType javascript let &errorformat = + \ '%E%.%#%n) %s:,' . + \ '%C%.%#Error: %m,' . + \ '%C%.%#at %s (%f:%l:%c),' . + \ '%Z%.%#at %s (%f:%l:%c),' . + \ '%-G%.%#,' +augroup END " }}} + +augroup git " {{{ + autocmd! + autocmd FileType gitcommit set textwidth=72 +augroup END +" }}} +" }}} + +" Leader commands {{{ + +" Edit specific files {{{ +nnoremap <silent> <leader>ev :split $MYVIMRC<CR> +nnoremap <silent> <leader>eb :split ~/.vim_bundles<CR> +nnoremap <silent> <leader>es :UltiSnipsEdit<CR> +nnoremap <silent> <leader>ea :split ~/.vim/after/plugin/abolish.vim<CR> + +nnoremap <silent> <leader>sv :so $MYVIMRC<CR> +nnoremap <silent> <leader>sb :so ~/.vim_bundles<CR> +nnoremap <silent> <leader>sa :so ~/.vim/after/plugin/abolish.vim<CR> + +nnoremap <Leader>el :EditSqlTempFile<CR> +" }}} + +" Toggle navigation panels {{{ +nnoremap <Leader>l :TagbarToggle<CR> +nnoremap <Leader>mb :MBEToggle<CR> +nnoremap <Leader>u :GundoToggle<CR> + +nnoremap <Leader>t :CtrlP<CR> +nnoremap <Leader>z :FZF<CR> +nnoremap <Leader>b :CtrlPBuffer<CR> +nnoremap <Leader>a :CtrlPTag<CR> +nnoremap <Leader>r :CtrlPGitBranch<CR> +" }}} + +" CtrlP {{{ +let g:ctrlp_custom_ignore = { + \ 'dir': 'node_modules', + \ } +" }}} + +" Git leader commands {{{ +noremap <Leader>g :Git<SPACE> +noremap <Leader>gu :Gpull<CR> +noremap <Leader>gp :Gpush<CR> +noremap <Leader>s :Gstatus<CR> +noremap <Leader>cv :Gcommit --verbose<CR> +noremap <Leader>ca :Gcommit --verbose --amend<CR> + +nnoremap <Leader>dl :diffg LOCAL<CR> +nnoremap <Leader>dr :diffg REMOTE<CR> +nnoremap <Leader>db :diffg BASE<CR> +nnoremap <Leader>du :diffu<CR> +nnoremap <Leader>dg :diffg<CR> + +nnoremap <Leader>d2 :diffg //2<CR>:diffu<CR> +nnoremap <Leader>d3 :diffg //3<CR>:diffu<CR> + +nnoremap <Leader>yt :SignifyToggle<CR> +" }}} + +" Breakpoint Leader Commands {{{ +nnoremap <Leader>x :Breakpoint<CR> +nnoremap <Leader>dx :BreakpointRemove *<CR> +" }}} + +" Tabularize {{{ + " Leader Commands {{{ + nnoremap <localleader>t= :Tabularize /=<CR> + vmap <localleader>t= :Tabularize /=<CR> + + nnoremap <localleader>t> :Tabularize /=><CR> + vmap <localleader>t> :Tabularize /=><CR> + " }}} + + " => Aligning {{{ + function! s:rocketalign() + let l:p = '^.*=>\s.*$' + echo l:p + if exists(':Tabularize') && getline('.') =~# '^.*=' && + \ (getline(line('.')-1) =~# l:p || getline(line('.')+1) =~# l:p) + let column = strlen(substitute(getline('.')[0:col('.')],'[^=>]','','g')) + let position = strlen(matchstr(getline('.')[0:col('.')],'.*=>\s*\zs.*')) + Tabularize/=>/l1 + normal! $ + call search(repeat('[^=>]*=>',column).'\s\{-\}'.repeat('.',position),'ce',line('.')) + endif + endfunction + "inoremap <buffer> <space>=><space> =><Esc>:call <SID>rocketalign()<CR>a + " }}} + + " = Aligning {{{ + function! s:eqalign() + let l:p = '^.*=\s.*$' + if exists(':Tabularize') && getline('.') =~# '^.*=' && + \ (getline(line('.')-1) =~# l:p || getline(line('.')+1) =~# l:p) + let column = strlen(substitute(getline('.')[0:col('.')],'[^=]','','g')) + let position = strlen(matchstr(getline('.')[0:col('.')],'.*=\s*\zs.*')) + Tabularize/=/l1 + normal! $ + call search(repeat('[^=]*=',column).'\s\{-\}'.repeat('.',position),'ce',line('.')) + endif + endfunction + "inoremap <buffer><silent> <space>=<space> =<Esc>:call <SID>eqalign()<CR>a + " }}} + + " : Aligning {{{ + function! s:colonalign() + let l:p : '^.*:\s.*$' + if exists(':Tabularize') && getline('.') :~# '^.*:' && + \ (getline(line('.')-1) :~# l:p || getline(line('.')+1) :~# l:p) + let column : strlen(substitute(getline('.')[0:col('.')],'[^:]','','g')) + let position : strlen(matchstr(getline('.')[0:col('.')],'.*:\s*\zs.*')) + Tabularize/:/l1 + normal! $ + call search(repeat('[^:]*:',column).'\s\{-\}'.repeat('.',position),'ce',line('.')) + endif + endfunction + "inoremap <buffer><silent> <space>:<space> :<Esc>:call <SID>colonalign()<CR>a + " }}} +" }}} + +" }}} + +" Mappings {{{ +" 'delete current' +nnoremap dc 0d$ +nnoremap com :silent !tmux set status<CR> +nnoremap <F9> :Make<CR> +nnoremap g<CR> :Dispatch<CR> +nnoremap g\ :Start<CR> +inoremap <F9> <ESC>:Make<CR>i + +" Navigate buffers {{{ +nnoremap gb :bn<CR> +nnoremap gB :bp<CR> +" }}} + +" Window Navigation {{{ +nnoremap <space>w <C-w> +nnoremap <space>h <C-w>h +nnoremap <space>j <C-w>j +nnoremap <space>k <C-w>k +nnoremap <space>l <C-w>l +nnoremap <space>z <C-w>z +" }}} + + +" Sort with motion {{{ +if !exists("g:sort_motion_flags") + let g:sort_motion_flags = "" +endif +function! s:sort_motion(mode) abort + if a:mode == 'line' + execute "'[,']sort " . g:sort_motion_flags + elseif a:mode == 'char' + execute "normal! `[v`]y" + let sorted = join(sort(split(@@, ', ')), ', ') + execute "normal! v`]c" . sorted + elseif a:mode == 'V' || a:mode == '' + execute "'<,'>sort " . g:sort_motion_flags + endif +endfunction + +function! s:sort_lines() + let beginning = line('.') + let end = v:count + beginning - 1 + execute beginning . ',' . end . 'sort' +endfunction + +xnoremap <silent> <Plug>SortMotionVisual :<C-U>call <SID>sort_motion(visualmode())<CR> +nnoremap <silent> <Plug>SortMotion :<C-U>set opfunc=<SID>sort_motion<CR>g@ +nnoremap <silent> <Plug>SortLines :<C-U>call <SID>sort_lines()<CR> + +map go <Plug>SortMotion +vmap go <Plug>SortMotionVisual +map goo <Plug>SortLines +" }}} +" }}} + +let g:hare_executable = 'cabal exec -- ghc-hare' diff --git a/users/grfn/system/home/modules/zshrc b/users/grfn/system/home/modules/zshrc new file mode 100644 index 000000000000..a12173d6842d --- /dev/null +++ b/users/grfn/system/home/modules/zshrc @@ -0,0 +1,327 @@ +#!/usr/bin/zsh +# vim: set fdm=marker fmr={{{,}}}: + +stty -ixon + +# Compinstall {{{ +zstyle ':completion:*' completer _complete _ignored _correct _approximate +zstyle ':completion:*' matcher-list '' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._- :]=** r:|=**' 'l:|=* r:|=*' +zstyle ':completion:*' max-errors 5 +zstyle ':completion:*' use-cache yes +zstyle ':completion::complete:grunt::options:' expire 1 +zstyle ':completion:*' prompt '%e errors' +zstyle :compinstall filename '~/.zshrc' +autoload -Uz compinit +compinit +# }}} + +# Zsh-newuser-install {{{ +HISTFILE=~/.histfile +HISTSIZE=1000 +SAVEHIST=1000 +setopt appendhistory autocd extendedglob notify autopushd +unsetopt beep nomatch +bindkey -v +# }}} + +# Basic options {{{ +set -o vi +umask 022 +export VIRTUAL_ENV_DISABLE_PROMPT=1 +# export PATH=~/.local/bin:~/.cabal/bin:$PATH:~/code/go/bin:~/bin:~/npm/bin:~/.gem/ruby/2.1.0/bin:~/.gem/ruby/2.0.0/bin:/home/smith/bin +# }}} + +# Zsh highlight highlighters {{{ +ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets pattern root) +# }}} + +# More basic options {{{ +setopt no_hist_verify +setopt histignorespace +# }}} + +# Utility Functions {{{ + +# Set the terminal's title bar. +function titlebar() { +echo -ne "\033]0;$*\007" +} + +function quiet() { +"$@" >/dev/null +} + +function quieter() { +"$@" >/dev/null 2>&1 +} + +# From http://stackoverflow.com/questions/370047/#370255 +function path_remove() { +IFS=: +# convert it to an array +t=($PATH) +unset IFS +# perform any array operations to remove elements from the array +t=(${t[@]%%$1}) +IFS=: +# output the new array +echo "${t[*]}" +} + +# }}} + +# Force screen to use zsh {{{ +# }}} + +# Environment {{{ +# }}} + +# Directory Stuff {{{ + +# Always use color output for `ls` + +# Directory listing + +# Easier navigation: .., ..., - + +# File size + +# Recursively delete `.DS_Store` files + +# Create a new directory and enter it +function md() { + mkdir -p "$@" && cd "$@" +} + +# }}} + +# MPD/MPC stuff {{{ +function mp() { +# Test if drive is already mounted +if ! lsblk | grep /media/external >/dev/null; then + if ! sudo mount /media/external; then + echo "External drive not plugged in, or could not mount" + return 1 + fi +fi +if (mpc >/dev/null 2>&1); then + ncmpcpp +else + mpd && + (pgrep mpdscribble || mpdscribble) && + ncmpcpp +fi +} + +# kill mp +function kmp() { +killall ncmpcpp +mpd --kill + +local files + +if (files=$(lsof 2>&1 | grep -v docker | grep external)); then + echo + echo "==> Still processes using external drive:" + echo + echo $files +else + sudo umount /media/external +fi +} + + +function mppal() { +mpc search album "$1" | mpc add && + mpc play; +} +# }}} + +# Git stuff {{{ +# function ga() { git add "${@:-.}"; } # Add all files by default +# Add non-whitespace changes +# function gc() { git checkout "${@:-master}"; } # Checkout master by default + +# open all changed files (that still actually exist) in the editor +function ged() { +local files=() +for f in $(git diff --name-only "$@"); do + [[ -e "$f" ]] && files=("${files[@]}" "$f") +done +local n=${#files[@]} +echo "Opening $n $([[ "$@" ]] || echo "modified ")file$([[ $n != 1 ]] && \ + echo s)${@:+ modified in }$@" +q "${files[@]}" +} + +# git find-replace +function gfr() { +if [[ "$#" == "0" ]]; then + echo 'Usage:' + echo ' gg_replace term replacement file_mask' + echo + echo 'Example:' + echo ' gg_replace cappuchino cappuccino *.html' + echo +else + find=$1; shift + replace=$1; shift + + ORIG_GLOBIGNORE=$GLOBIGNORE + GLOBIGNORE=*.* + if [[ "$#" = "0" ]]; then + set -- ' ' $@ + fi + + while [[ "$#" -gt "0" ]]; do + for file in `git grep -l $find -- $1`; do + sed -e "s/$find/$replace/g" -i'' $file + done + shift + done + + GLOBIGNORE=$ORIG_GLOBIGNORE +fi +} + +function vconflicts() { +$EDITOR $(git status --porcelain | awk '/^UU/ { print $2 }') +} +# }}} + +# fzf {{{ +v() { + local file + file=$(fzf-tmux --query="$1" --select-1 --exit-0) + [ -n "$file" ] && ${EDITOR:-vim} "$file" +} + +c() { + local dir + dir=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && cd "$dir" +} + +co() { + local branch + branch=$(git branch -a | sed -s "s/\s*\**//g" | fzf --query="$1" --select-1 --exit-0) && git checkout "$branch" +} + + +# fh - repeat history +# h() { +# eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//') +# } + +# fkill - kill process +fkill() { + ps -ef | sed 1d | fzf-tmux -m | awk '{print $2}' | xargs kill -${1:-9} +} +# }}} + +# Tmux utils {{{ +kill_detached() { + for sess in $(tmux ls | grep -v attached | sed -s "s/:.*$//"); do + tmux kill-session -t $sess; + done +} +# }}} + +# Docker {{{ + + +# dbp foo/bar . +function dbp () { + docker build -t $1 ${@:2} && docker push $1 +} + +# }}} + +# Twitter! {{{ + + +# favelast <username> +function favelast() { + t fave $(t tl -l $1 | head -n1 | first) +} + +function rtlast() { + t rt $(t tl -l $1 | head -n1 | first) +} + +function tthread() { + t reply $(t tl -l $TWITTER_WHOAMI | head -n1 | first) $@ +} +# }}} + +# Geeknote {{{ +gnc() { + gn create --title $1 --content '' && + gn find --count=1 "$1" + gn edit 1 +} +# }}} + +# Misc aliases {{{ + +function fw() { # fix white + local substitution + local substitution='s/\x1b\[90m/\x1b[92m/g' + $@ > >(perl -pe "$substitution") 2> >(perl -pe "$substitution" 1>&2) +} +# }}} + +# Grep options {{{ +unset GREP_OPTIONS +export GREP_OPTIONS= +# }}} + + +# Run docker containers {{{ + # -d \ + # -v $HOME/.pentadactyl:/home/firefox/.pentadactyl:rw \ + # -v $HOME/.pentadactylrc:/home/firefox/.pentadactylrc:rw \ + # -v $HOME/.mozilla:/home/firefox/.mozilla:rw \ + # -v $HOME/.config:/home/firefox/.config \ + # -v $HOME/Downloads:/home/firefox/Downloads:rw \ + # -v /etc/fonts:/etc/fonts \ + # -v /tmp/.X11-unix:/tmp/.X11-unix \ + # -v /dev/snd:/dev/snd \ + # --net=host \ + # -v $XDG_RUNTIME_DIR:$XDG_RUNTIME_DIR \ + # -e uid=$(id -u) \ + # -e gid=$(id -g) \ + # -e DISPLAY=$DISPLAY \ + # -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ + # --name firefox \ + # --rm -it \ + # glittershark/firefox +# }}} + +# Change cursor shape on insert/normal mode {{{ +# (https://unix.stackexchange.com/q/433273/64261) + +KEYTIMEOUT=5 + +_fix_cursor() { + echo -ne '\e[5 q' +} + +precmd_functions+=(_fix_cursor) + +function zle-keymap-select { + if [[ ${KEYMAP} == vicmd ]] || + [[ $1 = 'block' ]]; then + echo -ne '\e[1 q' + + elif [[ ${KEYMAP} == main ]] || + [[ ${KEYMAP} == viins ]] || + [[ ${KEYMAP} = '' ]] || + [[ $1 = 'beam' ]]; then + echo -ne '\e[5 q' + fi +} +zle -N zle-keymap-select + +# }}} + +[ -f ./.localrc ] && source ./.localrc diff --git a/users/grfn/system/home/platforms/darwin.nix b/users/grfn/system/home/platforms/darwin.nix new file mode 100644 index 000000000000..cf0375e94162 --- /dev/null +++ b/users/grfn/system/home/platforms/darwin.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + home.packages = with pkgs; [ + coreutils + gnupg + pinentry_mac + ]; + + home.activation.linkApplications = lib.hm.dag.entryAfter ["writeBoundary"] '' + $DRY_RUN_CMD ln -sf $VERBOSE_ARG \ + ~/.nix-profile/Applications/* ~/Applications/ + ''; + + programs.zsh.initExtra = '' + export NIX_PATH=$HOME/.nix-defexpr/channels:$NIX_PATH + + if [[ "$TERM" == "alacritty" ]]; then + export TERM="xterm-256color" + fi + ''; + }; +} diff --git a/users/grfn/system/home/platforms/linux.nix b/users/grfn/system/home/platforms/linux.nix new file mode 100644 index 000000000000..92924e419973 --- /dev/null +++ b/users/grfn/system/home/platforms/linux.nix @@ -0,0 +1,93 @@ +{ config, pkgs, ... }: + +let + + depot = config.lib.depot; + +in + +{ + imports = [ + ../modules/alacritty.nix + ../modules/alsi.nix + ../modules/development.nix + ../modules/emacs.nix + ../modules/email.nix + ../modules/firefox.nix + ../modules/games.nix + ../modules/obs.nix + ../modules/i3.nix + ../modules/shell.nix + ../modules/tarsnap.nix + ../modules/vim.nix + ]; + + xsession.enable = true; + + home.packages = with pkgs; [ + (import (fetchTarball "https://github.com/ashkitten/nixpkgs/archive/init-glimpse.tar.gz") {}).glimpse + + # Desktop stuff + arandr + firefox + feh + chromium + xclip + xorg.xev + picom + peek + signal-desktop + apvlv # pdf viewer + vlc + irssi + gnutls + pandoc + barrier + depot.tools.nsfv-setup + + # System utilities + powertop + usbutils + pciutils + gdmap + lsof + tree + ncat + iftop + + # Security + gnupg + keybase + openssl + yubikey-manager + yubikey-manager-qt + + # Spotify...etc + spotify + playerctl + ]; + + services.redshift = { + enable = true; + provider = "geoclue2"; + }; + + services.pasystray.enable = true; + + services.gpg-agent = { + enable = true; + }; + + gtk = { + enable = true; + gtk3.bookmarks = [ + "file:///home/grfn/code" + ]; + }; + + programs.zsh.initExtra = '' + [[ ! $IN_NIX_SHELL ]] && alsi -l + ''; + + services.lorri.enable = true; +} diff --git a/users/grfn/system/install b/users/grfn/system/install new file mode 100755 index 000000000000..a9a45953da07 --- /dev/null +++ b/users/grfn/system/install @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [[ -f /etc/nixos/.system-installed ]]; then + echo "=== System config already installed, skipping" +else + echo "==> Installing system config" + + [[ -d /etc/nixos ]] && sudo mv /etc/nixos{,.bak} + sudo mkdir -p /etc/nixos + sudo cp /etc/nixos.bak/hardware-configuration.nix /etc/nixos + + sudo cp ./system/configuration.nix /etc/nixos/ + sudo ln -s $(pwd)/system/{machines,modules,pkgs} /etc/nixos + sudo touch /etc/nixos/.system-installed + + echo "==> System config installed, your old configuration is at /etc/nixos.bak" +fi +echo + +if [[ -f ~/.config/nixpkgs/system-installed ]]; then + echo "=== home-manager config already installed, skipping" +else + echo "==> Installing home-manager config" + nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager + nix-channel --update + # nix-shell '<home-manager>' -A install + + [[ -d ~/.config/nixpkgs ]] && mv ~/.config/{nixpkgs,nixpkgs.bak} + mkdir -p ~/.config/nixpkgs + ln -s $(pwd)/home/* ~/.config/nixpkgs + + echo "==> home-manager config installed" +fi diff --git a/users/grfn/system/system/.skip-subtree b/users/grfn/system/system/.skip-subtree new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/grfn/system/system/.skip-subtree diff --git a/users/grfn/system/system/configuration.nix b/users/grfn/system/system/configuration.nix new file mode 100644 index 000000000000..eae567015b73 --- /dev/null +++ b/users/grfn/system/system/configuration.nix @@ -0,0 +1,11 @@ +{ config, pkgs, ... }: + +let machine = throw "Pick a machine from ./machines"; in +{ + imports = + [ + /etc/nixos/hardware-configuration.nix + ./modules/common.nix + machine + ]; +} diff --git a/users/grfn/system/system/default.nix b/users/grfn/system/system/default.nix new file mode 100644 index 000000000000..445d4ad2438d --- /dev/null +++ b/users/grfn/system/system/default.nix @@ -0,0 +1,38 @@ +args @ { depot, pkgs, ... }: + +rec { + mugwump = import ./machines/mugwump.nix; + + mugwumpSystem = (depot.ops.nixos.nixosFor mugwump).system; + + roswell = import ./machines/roswell.nix; + + roswellSystem = (depot.ops.nixos.nixosFor ({ ... }: { + imports = [ + ./machines/roswell.nix + "${pkgs.home-manager.src}/nixos" + ]; + + # Use the same nixpkgs as everything else + home-manager.useGlobalPkgs = true; + + home-manager.users.grfn = { config, lib, ... }: { + imports = [ ../home/machines/roswell.nix ]; + lib.depot = depot; + }; + })).system; + + yeren = import ./machines/yeren.nix; + + yerenSystem = (depot.ops.nixos.nixosFor yeren).system; + + iso = import ./iso.nix args; + + meta.targets = [ + "mugwumpSystem" + "roswellSystem" + "yerenSystem" + + "iso" + ]; +} diff --git a/users/grfn/system/system/iso.nix b/users/grfn/system/system/iso.nix new file mode 100644 index 000000000000..4adccebfb8a2 --- /dev/null +++ b/users/grfn/system/system/iso.nix @@ -0,0 +1,17 @@ +{ depot, lib, pkgs, ... }: + +let + configuration = { ... }: { + imports = [ + "${pkgs.path}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" + "${pkgs.path}/nixos/modules/installer/cd-dvd/channel.nix" + ]; + + networking.networkmanager.enable = true; + networking.useDHCP = false; + networking.firewall.enable = false; + networking.wireless.enable = lib.mkForce false; + }; +in (depot.third_party.nixos { + inherit configuration; +}).config.system.build.isoImage diff --git a/users/grfn/system/system/machines/bumblebee.nix b/users/grfn/system/system/machines/bumblebee.nix new file mode 100644 index 000000000000..0fec21409255 --- /dev/null +++ b/users/grfn/system/system/machines/bumblebee.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ../modules/reusable/battery.nix + ]; + + networking.hostName = "bumblebee"; + + powerManagement = { + enable = true; + cpuFreqGovernor = "powersave"; + powertop.enable = true; + }; + + # Hibernate on low battery + laptop.onLowBattery = { + enable = true; + action = "hibernate"; + thresholdPercentage = 5; + }; + + services.xserver.xkbOptions = "caps:swapescape"; +} diff --git a/users/grfn/system/system/machines/mugwump.nix b/users/grfn/system/system/machines/mugwump.nix new file mode 100644 index 000000000000..aae9b0387ff5 --- /dev/null +++ b/users/grfn/system/system/machines/mugwump.nix @@ -0,0 +1,260 @@ +{ config, lib, pkgs, modulesPath, depot, ... }: + +with lib; + +{ + imports = [ + ../modules/common.nix + (modulesPath + "/installer/scan/not-detected.nix") + "${depot.path}/ops/modules/prometheus-fail2ban-exporter.nix" + "${depot.path}/users/grfn/xanthous/server/module.nix" + ]; + + networking.hostName = "mugwump"; + + boot = { + loader.systemd-boot.enable = true; + + kernelModules = [ "kvm-intel" ]; + extraModulePackages = [ ]; + + initrd = { + availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ]; + kernelModules = [ + "uas" "usbcore" "usb_storage" "vfat" "nls_cp437" "nls_iso8859_1" + ]; + + postDeviceCommands = pkgs.lib.mkBefore '' + mkdir -m 0755 -p /key + sleep 2 + mount -n -t vfat -o ro `findfs UUID=9048-A9D5` /key + ''; + + luks.devices."cryptroot" = { + device = "/dev/disk/by-uuid/803a9028-339c-4617-a213-4fe138161f6d"; + keyFile = "/key/keyfile"; + preLVM = false; + }; + }; + }; + + fileSystems = { + "/" = { + device = "/dev/mapper/cryptroot"; + fsType = "btrfs"; + }; + "/boot" = { + device = "/dev/disk/by-uuid/7D74-0E4B"; + fsType = "vfat"; + }; + }; + + networking.interfaces = { + enp0s25.useDHCP = false; + wlp2s0.useDHCP = false; + }; + + networking.firewall.enable = true; + networking.firewall.allowedTCPPorts = [ 22 80 443 ]; + + security.sudo.extraRules = [{ + groups = ["wheel"]; + commands = [{ command = "ALL"; options = ["NOPASSWD"]; }]; + }]; + + nix.gc.dates = "monthly"; + + services.fail2ban = { + enable = true; + ignoreIP = [ + "172.16.0.0/16" + ]; + }; + + services.openssh = { + allowSFTP = false; + passwordAuthentication = false; + permitRootLogin = "no"; + }; + + services.grafana = { + enable = true; + port = 3000; + domain = "metrics.gws.fyi"; + rootUrl = "https://metrics.gws.fyi"; + dataDir = "/var/lib/grafana"; + analytics.reporting.enable = false; + + provision = { + enable = true; + datasources = [{ + name = "Prometheus"; + type = "prometheus"; + url = "http://localhost:9090"; + }]; + }; + }; + + security.acme.email = "root@gws.fyi"; + security.acme.acceptTerms = true; + + services.nginx = { + enable = true; + statusPage = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + + virtualHosts = { + "metrics.gws.fyi" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://localhost:${toString config.services.grafana.port}"; + }; + }; + }; + }; + + services.ddclient = { + enable = true; + domains = [ "home.gws.fyi" ]; + interval = "1d"; + zone = "gws.fyi"; + protocol = "cloudflare"; + username = "root@gws.fyi"; + quiet = true; + }; + + systemd.services.ddclient.serviceConfig = { + EnvironmentFile = "/etc/secrets/cloudflare.env"; + DynamicUser = lib.mkForce false; + ExecStart = lib.mkForce ( + let runtimeDir = + config.systemd.services.ddclient.serviceConfig.RuntimeDirectory; + in pkgs.writeShellScript "ddclient" '' + set -eo pipefail + + ${pkgs.gnused}/bin/sed -i -s s/password=/password=$CLOUDFLARE_API_KEY/ /run/${runtimeDir}/ddclient.conf + exec ${pkgs.ddclient}/bin/ddclient \ + -file /run/${runtimeDir}/ddclient.conf \ + -login=$CLOUDFLARE_EMAIL \ + ''); + }; + + security.acme.certs."metrics.gws.fyi" = { + dnsProvider = "cloudflare"; + credentialsFile = "/etc/secrets/cloudflare.env"; + webroot = mkForce null; + }; + + services.prometheus = { + enable = true; + exporters = { + node = { + enable = true; + openFirewall = false; + + enabledCollectors = [ + "processes" + "systemd" + "tcpstat" + "wifi" + ]; + }; + + nginx = { + enable = true; + openFirewall = true; + sslVerify = false; + constLabels = [ "host=mugwump" ]; + }; + + blackbox = { + enable = true; + openFirewall = true; + configFile = pkgs.writeText "blackbox-exporter.yaml" (builtins.toJSON { + modules = { + https_2xx = { + prober = "http"; + http = { + method = "GET"; + fail_if_ssl = false; + fail_if_not_ssl = true; + preferred_ip_protocol = "ip4"; + }; + }; + }; + }); + }; + }; + + scrapeConfigs = [{ + job_name = "node"; + scrape_interval = "5s"; + static_configs = [{ + targets = ["localhost:${toString config.services.prometheus.exporters.node.port}"]; + }]; + } { + job_name = "nginx"; + scrape_interval = "5s"; + static_configs = [{ + targets = ["localhost:${toString config.services.prometheus.exporters.nginx.port}"]; + }]; + } { + job_name = "xanthous_server"; + scrape_interval = "1s"; + static_configs = [{ + targets = ["localhost:${toString config.services.xanthous-server.metricsPort}"]; + }]; + } { + job_name = "blackbox"; + metrics_path = "/probe"; + params.module = ["https_2xx"]; + scrape_interval = "5s"; + static_configs = [{ + targets = [ + "https://gws.fyi" + "https://windtunnel.ci" + "https://app.windtunnel.ci" + "https://metrics.gws.fyi" + ]; + }]; + relabel_configs = [{ + source_labels = ["__address__"]; + target_label = "__param_target"; + } { + source_labels = ["__param_target"]; + target_label = "instance"; + } { + target_label = "__address__"; + replacement = "localhost:${toString config.services.prometheus.exporters.blackbox.port}"; + }]; + }]; + }; + + services.xanthous-server.enable = true; + + virtualisation.docker.enable = true; + + services.buildkite-agents = listToAttrs (map (n: rec { + name = "mugwump-${toString n}"; + value = { + inherit name; + enable = true; + tokenPath = "/etc/secrets/buildkite-agent-token"; + privateSshKeyPath = "/etc/secrets/buildkite-ssh-key"; + runtimePackages = with pkgs; [ + docker + nix + gnutar + gzip + ]; + }; + }) (range 1 1)); + + users.users."buildkite-agent-mugwump-1" = { + isSystemUser = true; + extraGroups = [ "docker" ]; + }; +} diff --git a/users/grfn/system/system/machines/roswell.nix b/users/grfn/system/system/machines/roswell.nix new file mode 100644 index 000000000000..6eb4a510b8cd --- /dev/null +++ b/users/grfn/system/system/machines/roswell.nix @@ -0,0 +1,17 @@ +{ depot, config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ + ../modules/common.nix + "${modulesPath}/installer/scan/not-detected.nix" + "${modulesPath}/virtualisation/amazon-image.nix" + ]; + + ec2.hvm = true; + + networking.hostName = "roswell"; + + users.users.grfn.openssh.authorizedKeys.keys = [ + depot.users.grfn.keys.main + ]; +} diff --git a/users/grfn/system/system/machines/yeren.nix b/users/grfn/system/system/machines/yeren.nix new file mode 100644 index 000000000000..c6aff886de2f --- /dev/null +++ b/users/grfn/system/system/machines/yeren.nix @@ -0,0 +1,132 @@ +{ depot, modulesPath, config, lib, pkgs, ... }: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ../modules/common.nix + ../modules/laptop.nix + ../modules/xserver.nix + ../modules/fonts.nix + ../modules/sound.nix + ../modules/tvl.nix + ../modules/development.nix + ../modules/work/kolide.nix + ]; + + networking.hostName = "yeren"; + + system.stateVersion = "21.03"; + + boot = { + initrd = { + availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ]; + kernelModules = [ ]; + + luks.devices = { + "cryptroot".device = "/dev/disk/by-uuid/dcfbc22d-e0d2-411b-8dd3-96704d3aae2e"; + }; + }; + + kernelPackages = pkgs.linuxPackages_5_14; + + kernelModules = [ "kvm-intel" ]; + blacklistedKernelModules = [ "psmouse" ]; + extraModulePackages = [ + config.boot.kernelPackages.digimend + ]; + kernelParams = [ + "i915.preliminary_hw_support=1" + "pcie_aspm=force" + ]; + + # https://bbs.archlinux.org/viewtopic.php?pid=1933643#p1933643 + extraModprobeConfig = '' + options snd-intel-dspcfg dsp_driver=1 + ''; + + kernel.sysctl = { + "kernel.perf_event_paranoid" = -1; + }; + }; + + fileSystems = { + "/" = { + device = "/dev/mapper/cryptroot"; + fsType = "btrfs"; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/53A9-248B"; + fsType = "vfat"; + }; + }; + + swapDevices = [{ + device = "/dev/disk/by-uuid/b627cb0e-0451-4f25-94d0-6497e01f0da4"; + }]; + + services.xserver = { + exportConfiguration = true; + extraConfig = '' + Section "Device" + Identifier "Intel Graphics" + Driver "intel" + Option "TripleBuffer" "true" + Option "TearFree" "true" + Option "DRI" "true" + Option "AccelMethod" "sna" + EndSection + ''; + }; + + hardware.firmware = with pkgs; [ + alsa-firmware + sof-firmware + ]; + + hardware.opengl.extraPackages = with pkgs; [ + vaapiIntel + vaapiVdpau + libvdpau-va-gl + intel-media-driver + ]; + + # Disabled for now until libfprint-tod can get a version bump + # services.fprintd = { + # enable = true; + # package = pkgs.fprintd-tod; + # }; + + systemd.services.fprintd.environment.FP_TOD_DRIVERS_DIR = + "${pkgs.libfprint-2-tod1-goodix}/usr/lib/libfprint-2/tod-1"; + + security.pam.loginLimits = [ + { + domain = "grfn"; + type = "soft"; + item = "nofile"; + value = "65535"; + } + ]; + + security.pam.services = { + login.fprintAuth = true; + sudo.fprintAuth = true; + i3lock.fprintAuth = false; + i3lock-color.fprintAuth = false; + lightdm.fprintAuth = true; + lightdm-greeter.fprintAuth = true; + }; + + hardware.opengl.driSupport32Bit = true; + + hardware.pulseaudio.extraConfig = '' + load-module module-remap-source source_name=KompleteAudio6_1 source_properties=device.description=KompleteAudio6Input1 master=alsa_input.usb-Native_Instruments_Komplete_Audio_6_458E0FFD-00.multichannel-input remix=no channels=1 master_channel_map=front-left channel_map=mono + load-module module-remap-source source_name=KompleteAudio6_2 source_properties=device.description=KompleteAudio6Input2 master=alsa_input.usb-Native_Instruments_Komplete_Audio_6_458E0FFD-00.multichannel-input remix=no channels=1 master_channel_map=front-right channel_map=mono + load-module module-remap-sink sink_name=KompleteAudio6_12 sink_properties=device.description=KompleteAudio6_12 remix=no master=alsa_output.usb-Native_Instruments_Komplete_Audio_6_458E0FFD-00.analog-surround-21 channels=2 master_channel_map=front-left,front-right channel_map=front-left,front-right + ''; + + services.fwupd.enable = true; + + services.tailscale.enable = true; +} diff --git a/users/grfn/system/system/modules/common.nix b/users/grfn/system/system/modules/common.nix new file mode 100644 index 000000000000..a91584680d52 --- /dev/null +++ b/users/grfn/system/system/modules/common.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +let + + depot = import ../../../../.. {}; + +in + +with lib; + +{ + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + networking.useDHCP = false; + networking.networkmanager.enable = true; + + i18n = { + defaultLocale = "en_US.UTF-8"; + }; + + time.timeZone = lib.mkDefault "America/New_York"; + + environment.systemPackages = with pkgs; [ + wget + vim + zsh + git + w3m + libnotify + file + lm_sensors + dnsutils + htop + man-pages + man-pages-posix + ]; + + documentation.dev.enable = true; + documentation.man.generateCaches = true; + + services.openssh.enable = true; + + programs.ssh.startAgent = true; + + networking.firewall.enable = mkDefault false; + + users.mutableUsers = true; + programs.zsh.enable = true; + environment.pathsToLink = [ "/share/zsh" ]; + users.users.grfn = { + isNormalUser = true; + initialPassword = "password"; + extraGroups = [ + "wheel" + "networkmanager" + "audio" + "docker" + ]; + shell = pkgs.zsh; + }; + + nix = { + trustedUsers = [ "grfn" ]; + autoOptimiseStore = true; + distributedBuilds = true; + + gc = { + automatic = true; + dates = mkDefault "weekly"; + options = "--delete-older-than 30d"; + }; + }; + + services.udev.packages = with pkgs; [ + yubikey-personalization + ]; + + services.pcscd.enable = true; +} diff --git a/users/grfn/system/system/modules/desktop.nix b/users/grfn/system/system/modules/desktop.nix new file mode 100644 index 000000000000..3adbd9d9b07f --- /dev/null +++ b/users/grfn/system/system/modules/desktop.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./xserver.nix + ./fonts.nix + ./sound.nix + ./kernel.nix + ]; + + programs.nm-applet.enable = true; + + users.users.grfn.extraGroups = [ + "audio" + "video" + ]; + + services.geoclue2.enable = true; +} diff --git a/users/grfn/system/system/modules/development.nix b/users/grfn/system/system/modules/development.nix new file mode 100644 index 000000000000..bfa0e22cff0a --- /dev/null +++ b/users/grfn/system/system/modules/development.nix @@ -0,0 +1,6 @@ +{ config, lib, pkgs, ... }: + +{ + virtualisation.docker.enable = true; + users.users.grfn.extraGroups = [ "docker" ]; +} diff --git a/users/grfn/system/system/modules/fcitx.nix b/users/grfn/system/system/modules/fcitx.nix new file mode 100644 index 000000000000..812f598f9f47 --- /dev/null +++ b/users/grfn/system/system/modules/fcitx.nix @@ -0,0 +1,10 @@ +{ config, lib, pkgs, ... }: + +{ + i18n.inputMethod = { + enabled = "fcitx"; + fcitx.engines = with pkgs.fcitx-engines; [ + cloudpinyin + ]; + }; +} diff --git a/users/grfn/system/system/modules/fonts.nix b/users/grfn/system/system/modules/fonts.nix new file mode 100644 index 000000000000..babe30d4271f --- /dev/null +++ b/users/grfn/system/system/modules/fonts.nix @@ -0,0 +1,12 @@ +{ config, lib, pkgs, ... }: +{ + fonts = { + fonts = with pkgs; [ + nerdfonts + noto-fonts-emoji + twitter-color-emoji + ]; + + fontconfig.defaultFonts.emoji = ["Twitter Color Emoji"]; + }; +} diff --git a/users/grfn/system/system/modules/kernel.nix b/users/grfn/system/system/modules/kernel.nix new file mode 100644 index 000000000000..dd213fcca31c --- /dev/null +++ b/users/grfn/system/system/modules/kernel.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: +with lib.versions; +let + inherit (pkgs) stdenvNoCC; + kernelRelease = config.boot.kernelPackages.kernel.version; + mj = major kernelRelease; + mm = majorMinor kernelRelease; + patched-linux-ck = stdenvNoCC.mkDerivation { + name = "linux-ck"; + src = builtins.fetchurl { + name = "linux-ck-patch-${mm}-ck1.xz"; + # example: http://ck.kolivas.org/patches/5.0/5.4/5.4-ck1/patch-5.4-ck1.xz + url = "http://ck.kolivas.org/patches/${mj}.0/${mm}/${mm}-ck1/patch-${mm}-ck1.xz"; + sha256 = "1kka38rmjcqsv4j2anczrsni0bf6yfdx2vsxbna3ic84nh3rz434"; + }; + + unpackPhase = '' + ${pkgs.xz}/bin/unxz -kfdc $src > patch-${mm}-ck1 + ''; + + installPhase = '' + cp patch-${mm}-ck1 $out + ''; + }; +in +{ + boot.kernelPackages = pkgs.linuxPackages_5_10.extend (self: super: { + kernel = super.kernel.override { + ignoreConfigErrors = true; + kernelPatches = super.kernel.kernelPatches ++ [{ + name = "linux-ck"; + patch = patched-linux-ck; + }]; + argsOverride = { + modDirVersion = super.kernel.modDirVersion + "-ck1"; + }; + }; + }); +} diff --git a/users/grfn/system/system/modules/laptop.nix b/users/grfn/system/system/modules/laptop.nix new file mode 100644 index 000000000000..05c5333e513f --- /dev/null +++ b/users/grfn/system/system/modules/laptop.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./reusable/battery.nix + ]; + + laptop.onLowBattery.enable = true; + + services.logind.extraConfig = '' + HandlePowerKey=hibernate + ''; + + services.tlp.enable = true; +} diff --git a/users/grfn/system/system/modules/reusable/README.org b/users/grfn/system/system/modules/reusable/README.org new file mode 100644 index 000000000000..34d9bfdcb729 --- /dev/null +++ b/users/grfn/system/system/modules/reusable/README.org @@ -0,0 +1,2 @@ +This directory contains things I'm eventually planning on contributing upstream +to nixpkgs diff --git a/users/grfn/system/system/modules/reusable/battery.nix b/users/grfn/system/system/modules/reusable/battery.nix new file mode 100644 index 000000000000..ca92e0c3f61c --- /dev/null +++ b/users/grfn/system/system/modules/reusable/battery.nix @@ -0,0 +1,32 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + options = { + laptop.onLowBattery = { + enable = mkEnableOption "Perform action on low battery"; + + thresholdPercentage = mkOption { + description = "Threshold battery percentage on which to perform the action"; + default = 8; + type = types.int; + }; + + action = mkOption { + description = "Action to perform on low battery"; + default = "hibernate"; + type = types.enum [ "hibernate" "suspend" "suspend-then-hibernate" ]; + }; + }; + }; + + config = + let cfg = config.laptop.onLowBattery; + in mkIf cfg.enable { + services.udev.extraRules = concatStrings [ + ''SUBSYSTEM=="power_supply", '' + ''ATTR{status}=="Discharging", '' + ''ATTR{capacity}=="[0-${toString cfg.thresholdPercentage}]", '' + ''RUN+="${pkgs.systemd}/bin/systemctl ${cfg.action}"'' + ]; + }; +} diff --git a/users/grfn/system/system/modules/rtlsdr.nix b/users/grfn/system/system/modules/rtlsdr.nix new file mode 100644 index 000000000000..ce58ebb0dcda --- /dev/null +++ b/users/grfn/system/system/modules/rtlsdr.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: + +{ + + environment.systemPackages = with pkgs; [ + rtl-sdr + ]; + + services.udev.packages = with pkgs; [ + rtl-sdr + ]; + + # blacklist for rtl-sdr + boot.blacklistedKernelModules = [ + "dvb_usb_rtl28xxu" + ]; +} diff --git a/users/grfn/system/system/modules/sound.nix b/users/grfn/system/system/modules/sound.nix new file mode 100644 index 000000000000..07a67a1ec43b --- /dev/null +++ b/users/grfn/system/system/modules/sound.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +{ + # Enable sound. + sound.enable = true; + hardware.pulseaudio.enable = true; + + environment.systemPackages = with pkgs; [ + pulseaudio-ctl + paprefs + pasystray + pavucontrol + ]; + + hardware.pulseaudio.package = pkgs.pulseaudioFull; +} diff --git a/users/grfn/system/system/modules/tvl.nix b/users/grfn/system/system/modules/tvl.nix new file mode 100644 index 000000000000..905ec8ced537 --- /dev/null +++ b/users/grfn/system/system/modules/tvl.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +{ + nix = { + buildMachines = [{ + hostName = "whitby.tvl.fyi"; + sshUser = "grfn"; + sshKey = "/root/.ssh/id_rsa"; + system = "x86_64-linux"; + maxJobs = 64; + supportedFeatures = ["big-parallel" "kvm" "nixos-test" "benchmark"]; + }]; + + extraOptions = '' + builders-use-substitutes = true + ''; + + binaryCaches = [ + "https://cache.nixos.org" + "ssh://nix-ssh@whitby.tvl.fyi" + ]; + trustedBinaryCaches = [ + "https://cache.nixos.org" + "ssh://nix-ssh@whitby.tvl.fyi" + ]; + binaryCachePublicKeys = [ + "cache.tvl.fyi:fd+9d1ceCPvDX/xVhcfv8nAa6njEhAGAEe+oGJDEeoc=" + ]; + }; + + programs.ssh.knownHosts.whitby = { + hostNames = [ "whitby" "whitby.tvl.fyi" "49.12.129.211"]; + publicKeyFile = pkgs.writeText "whitby.pub" '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I + ''; + }; +} diff --git a/users/grfn/system/system/modules/work/kolide.deb b/users/grfn/system/system/modules/work/kolide.deb new file mode 100644 index 000000000000..a319a5806fca --- /dev/null +++ b/users/grfn/system/system/modules/work/kolide.deb Binary files differdiff --git a/users/grfn/system/system/modules/work/kolide.nix b/users/grfn/system/system/modules/work/kolide.nix new file mode 100644 index 000000000000..29ee0a0d7ce4 --- /dev/null +++ b/users/grfn/system/system/modules/work/kolide.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +let + deb = ./kolide.deb; + + kolide = pkgs.runCommand "kolide-data" { + buildInputs = [ pkgs.binutils-unwrapped ]; + } '' + cp ${deb} ./kolide.deb + ar x kolide.deb + mkdir result + tar xzf data.tar.gz -C result + patchelf \ + --set-interpreter ${pkgs.glibc}/lib/ld-linux-x86-64.so.2 \ + --set-rpath "${lib.makeLibraryPath (with pkgs; [ + zlib + ])}" \ + result/usr/local/kolide-k2/bin/osqueryd + mv result $out + ''; + +in { + systemd.services."launcher.kolide-k2" = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "syslog.service" ]; + description = "The Kolide Launcher"; + serviceConfig = { + ExecStart = '' + ${kolide}/usr/local/kolide-k2/bin/launcher \ + -config \ + ${pkgs.writeText "launcher.flags" '' + with_initial_runner + control + autoupdate + root_directory /var/lib/kolide + osqueryd_path ${kolide}/usr/local/kolide-k2/bin/osqueryd + enroll_secret_path ${kolide}/etc/kolide-k2/secret + control_hostname k2control.kolide.com + update_channel stable + transport jsonrpc + hostname k2device.kolide.com + ''} + ''; + StateDirectory = "kolide"; + Restart = "on-failure"; + RestartSec = 3; + }; + }; +} diff --git a/users/grfn/system/system/modules/xserver.nix b/users/grfn/system/system/modules/xserver.nix new file mode 100644 index 000000000000..35ee44112ea1 --- /dev/null +++ b/users/grfn/system/system/modules/xserver.nix @@ -0,0 +1,16 @@ +{ config, pkgs, ... }: +{ + # Enable the X11 windowing system. + services.xserver = { + enable = true; + layout = "us"; + + libinput.enable = true; + + displayManager = { + defaultSession = "none+i3"; + }; + + windowManager.i3.enable = true; + }; +} diff --git a/users/grfn/wigglydonke.rs/index.html b/users/grfn/wigglydonke.rs/index.html new file mode 100644 index 000000000000..4fd7f25fcf8c --- /dev/null +++ b/users/grfn/wigglydonke.rs/index.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <title>Kids Love Wiggly Donkers!</title> + <style type="text/css"> + #wiggly-donkers { + width: 100%; + } + </style> + </head> + <body> + <a href="https://tvl.fyi"> + <img src="/wd.png" id="wiggly-donkers"/> + </a> + </body> +</html> diff --git a/users/grfn/wigglydonke.rs/wd.png b/users/grfn/wigglydonke.rs/wd.png new file mode 100644 index 000000000000..217443e2df82 --- /dev/null +++ b/users/grfn/wigglydonke.rs/wd.png Binary files differdiff --git a/users/grfn/xanthous/.envrc b/users/grfn/xanthous/.envrc new file mode 100644 index 000000000000..be81feddb1a5 --- /dev/null +++ b/users/grfn/xanthous/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" \ No newline at end of file diff --git a/users/grfn/xanthous/.github/actions/nix-build/Dockerfile b/users/grfn/xanthous/.github/actions/nix-build/Dockerfile new file mode 100644 index 000000000000..cfe8e35df091 --- /dev/null +++ b/users/grfn/xanthous/.github/actions/nix-build/Dockerfile @@ -0,0 +1,23 @@ +FROM lnl7/nix:2.1.2 + +LABEL name="Nix Build for GitHub Actions" +LABEL version="1.0" +LABEL repository="http://github.com/glittershark/xanthous" +LABEL homepage="http://github.com/glittershark/xanthous" +LABEL maintainer="Griffin Smith <root at gws dot fyi>" + +LABEL "com.github.actions.name"="Nix Build" +LABEL "com.github.actions.description"="Runs 'nix-build'" +LABEL "com.github.actions.icon"="cpu" +LABEL "com.github.actions.color"="purple" + +RUN nix-env -iA \ + nixpkgs.gnutar nixpkgs.gzip \ + nixpkgs.gnugrep nixpkgs.git && \ + mkdir -p /etc/nix && \ + (echo "binary-caches = https://cache.nixos.org/" | tee -a /etc/nix/nix.conf) && \ + (echo "trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" | tee -a /etc/nix/nix.conf) + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT [ "/entrypoint.sh" ] +CMD [ "--help" ] diff --git a/users/grfn/xanthous/.github/actions/nix-build/entrypoint.sh b/users/grfn/xanthous/.github/actions/nix-build/entrypoint.sh new file mode 100755 index 000000000000..cb7aca541a3f --- /dev/null +++ b/users/grfn/xanthous/.github/actions/nix-build/entrypoint.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Entrypoint that runs nix-build and, optionally, copies Docker image tarballs +# to real files. The reason this is necessary is because once a Nix container +# exits, you must copy out the artifacts to the working directory before exit. + +[ "$DEBUG" = "1" ] && set -x +[ "$QUIET" = "1" ] && QUIET_ARG="-Q" + +set -e + +# file to build (e.g. release.nix) +file="$1" + +[ "$file" = "" ] && echo "No .nix file to build specified!" && exit 1 +[ ! -e "$file" ] && echo "File $file not exist!" && exit 1 + +echo "Building all attrs in $file..." +nix-build --no-link ${QUIET_ARG} "$file" "${@:2}" + +echo "Copying build closure to $(pwd)/store..." +mapfile -t storePaths < <(nix-build ${QUIET_ARG} --no-link "$file" | grep -v cache-deps) +printf '%s\n' "${storePaths[@]}" > store.roots +nix copy --to "file://$(pwd)/store" "${storePaths[@]}" diff --git a/users/grfn/xanthous/.github/workflows/haskell.yml b/users/grfn/xanthous/.github/workflows/haskell.yml new file mode 100644 index 000000000000..df82de3e8caf --- /dev/null +++ b/users/grfn/xanthous/.github/workflows/haskell.yml @@ -0,0 +1,15 @@ +name: Haskell CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Nix Build + with: + args: default.nix --arg failOnWarnings true + uses: ./.github/actions/nix-build diff --git a/users/grfn/xanthous/.gitignore b/users/grfn/xanthous/.gitignore new file mode 100644 index 000000000000..2ad31c01d443 --- /dev/null +++ b/users/grfn/xanthous/.gitignore @@ -0,0 +1,37 @@ +dist +dist-* +cabal-dev +*.o +*.hi +*.hie +*.chi +*.chs.h +*.dyn_o +*.dyn_hi +.hpc +.hsenv +.cabal-sandbox/ +cabal.sandbox.config +*.prof +*.aux +*.hp +*.eventlog +.stack-work/ +cabal.project.local +cabal.project.local~ +cabal.project.local~* +.HTF/ +.ghc.environment.* + + +# from nix-build +result + +# grr +*_flymake.hs + +# app-specific +debug.log +data +*.save +.tasty-rerun-log diff --git a/users/grfn/xanthous/LICENSE b/users/grfn/xanthous/LICENSE new file mode 100644 index 000000000000..45644ff76449 --- /dev/null +++ b/users/grfn/xanthous/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/users/grfn/xanthous/README.org b/users/grfn/xanthous/README.org new file mode 100644 index 000000000000..7e1fedb069b1 --- /dev/null +++ b/users/grfn/xanthous/README.org @@ -0,0 +1,36 @@ +#+TITLE: Xanthous + +* Building + +#+BEGIN_SRC shell +$ nix build +#+END_SRC + +* Running + +#+BEGIN_SRC shell +$ ./result/bin/xanthous [--help] +#+END_SRC + +** Keyboard commands + +Keyboard commands are currently undocumented, but can be found in [[[https://github.com/glittershark/xanthous/blob/master/src/Xanthous/Command.hs#L26][this file]]. +Movement uses the nethack-esque hjklybnu. + +* Development + +Use [[https://github.com/target/lorri][lorri]], or run everything in a ~nix-shell~ + +#+BEGIN_SRC shell +# Build (for dev) +$ cabal new-build + +# Run the game +$ cabal new-run xanthous + +# Run tests +$ cabal new-run test + +# Run a repl +$ cabal new-repl +#+END_SRC diff --git a/users/grfn/xanthous/Setup.hs b/users/grfn/xanthous/Setup.hs new file mode 100644 index 000000000000..9a994af677b0 --- /dev/null +++ b/users/grfn/xanthous/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/users/grfn/xanthous/app/Main.hs b/users/grfn/xanthous/app/Main.hs new file mode 100644 index 000000000000..c771a0d932cb --- /dev/null +++ b/users/grfn/xanthous/app/Main.hs @@ -0,0 +1,171 @@ +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Main ( main ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (finally) +import Brick +import qualified Brick.BChan +import qualified Graphics.Vty as Vty +import qualified Options.Applicative as Opt +import System.Random +import Control.Monad.Random (getRandom) +import Control.Exception (finally) +import System.Exit (die) +-------------------------------------------------------------------------------- +import qualified Xanthous.Game as Game +import Xanthous.Game.Env (GameEnv(..)) +import qualified Xanthous.Game.Env as Game +import Xanthous.App +import Xanthous.Generators.Level + ( GeneratorInput + , parseGeneratorInput + , generateFromInput + , showCells + ) +import qualified Xanthous.Entities.Character as Character +import Xanthous.Generators.Level.Util (regions) +import Xanthous.Generators.Level.LevelContents +import Xanthous.Data (Dimensions, Dimensions'(Dimensions)) +import Data.Array.IArray ( amap ) +-------------------------------------------------------------------------------- + +parseGameConfig :: Opt.Parser Game.Config +parseGameConfig = Game.Config + <$> Opt.switch + ( Opt.long "disable-saving" + <> Opt.help "Disallow saving games" + ) + +data RunParams = RunParams + { seed :: Maybe Int + , characterName :: Maybe Text + , gameConfig :: Game.Config + } + deriving stock (Show, Eq) + +parseRunParams :: Opt.Parser RunParams +parseRunParams = RunParams + <$> optional (Opt.option Opt.auto + ( Opt.long "seed" + <> Opt.help "Random seed for the game." + )) + <*> optional (Opt.strOption + ( Opt.short 'n' + <> Opt.long "name" + <> Opt.help + ( "Name for the character. If not set on the command line, " + <> "will be prompted for at runtime" + ) + )) + <*> parseGameConfig + +data Command + = Run RunParams + | Load FilePath + | Generate GeneratorInput Dimensions (Maybe Int) + +parseDimensions :: Opt.Parser Dimensions +parseDimensions = Dimensions + <$> Opt.option Opt.auto + ( Opt.short 'w' + <> Opt.long "width" + <> Opt.metavar "TILES" + ) + <*> Opt.option Opt.auto + ( Opt.short 'h' + <> Opt.long "height" + <> Opt.metavar "TILES" + ) + + +parseCommand :: Opt.Parser Command +parseCommand = (<|> Run <$> parseRunParams) $ Opt.subparser + $ Opt.command "run" + (Opt.info + (Run <$> parseRunParams) + (Opt.progDesc "Run the game")) + <> Opt.command "load" + (Opt.info + (Load <$> Opt.argument Opt.str (Opt.metavar "FILE")) + (Opt.progDesc "Load a saved game")) + <> Opt.command "generate" + (Opt.info + (Generate + <$> parseGeneratorInput + <*> parseDimensions + <*> optional + (Opt.option Opt.auto (Opt.long "seed")) + <**> Opt.helper + ) + (Opt.progDesc "Generate a sample level")) + +optParser :: Opt.ParserInfo Command +optParser = Opt.info + (parseCommand <**> Opt.helper) + (Opt.header "Xanthous: a WIP TUI RPG") + +thanks :: IO () +thanks = putStr "\n\n" >> putStrLn "Thanks for playing Xanthous!" + +newGame :: RunParams -> IO () +newGame rparams = do + gameSeed <- maybe getRandom pure $ seed rparams + when (isNothing $ seed rparams) + . putStrLn + $ "Seed: " <> tshow gameSeed + let initialState = Game.initialStateFromSeed gameSeed &~ do + for_ (characterName rparams) $ \cn -> + Game.character . Character.characterName ?= cn + runGame NewGame (gameConfig rparams) initialState `finally` do + thanks + when (isNothing $ seed rparams) + . putStrLn + $ "Seed: " <> tshow gameSeed + putStr "\n\n" + +loadGame :: FilePath -> IO () +loadGame saveFile = do + gameState <- maybe (die "Invalid save file!") pure . Game.loadGame . fromStrict + =<< readFile @IO saveFile + gameState `deepseq` runGame (LoadGame saveFile) Game.defaultConfig gameState + +runGame :: RunType -> Game.Config -> Game.GameState -> IO () +runGame rt _config gameState = do + _eventChan <- Brick.BChan.newBChan 10 + let gameEnv = GameEnv {..} + app <- makeApp gameEnv rt + let buildVty = Vty.mkVty Vty.defaultConfig + initialVty <- buildVty + _game' <- customMain + initialVty + buildVty + (Just _eventChan) + app + gameState + pure () + +runGenerate :: GeneratorInput -> Dimensions -> Maybe Int -> IO () +runGenerate input dims mSeed = do + putStrLn "Generating..." + genSeed <- maybe getRandom pure mSeed + let randGen = mkStdGen genSeed + res = generateFromInput input dims randGen + rs = regions $ amap not res + when (isNothing mSeed) + . putStrLn + $ "Seed: " <> tshow genSeed + putStr "num regions: " + print $ length rs + putStr "region lengths: " + print $ length <$> rs + putStr "character position: " + print =<< chooseCharacterPosition res + putStrLn $ showCells res + +runCommand :: Command -> IO () +runCommand (Run runParams) = newGame runParams +runCommand (Load saveFile) = loadGame saveFile +runCommand (Generate input dims mSeed) = runGenerate input dims mSeed + +main :: IO () +main = runCommand =<< Opt.execParser optParser diff --git a/users/grfn/xanthous/bench/Bench.hs b/users/grfn/xanthous/bench/Bench.hs new file mode 100644 index 000000000000..5889618ee432 --- /dev/null +++ b/users/grfn/xanthous/bench/Bench.hs @@ -0,0 +1,12 @@ +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import Bench.Prelude +-------------------------------------------------------------------------------- +import qualified Xanthous.RandomBench +import qualified Xanthous.Generators.UtilBench + +main :: IO () +main = defaultMain + [ Xanthous.Generators.UtilBench.benchmark + ] diff --git a/users/grfn/xanthous/bench/Bench/Prelude.hs b/users/grfn/xanthous/bench/Bench/Prelude.hs new file mode 100644 index 000000000000..c553abd6d5d0 --- /dev/null +++ b/users/grfn/xanthous/bench/Bench/Prelude.hs @@ -0,0 +1,9 @@ +-------------------------------------------------------------------------------- +module Bench.Prelude + ( module Xanthous.Prelude + , module Criterion.Main + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Criterion.Main +-------------------------------------------------------------------------------- diff --git a/users/grfn/xanthous/bench/Xanthous/Generators/UtilBench.hs b/users/grfn/xanthous/bench/Xanthous/Generators/UtilBench.hs new file mode 100644 index 000000000000..56310e691c33 --- /dev/null +++ b/users/grfn/xanthous/bench/Xanthous/Generators/UtilBench.hs @@ -0,0 +1,37 @@ +-------------------------------------------------------------------------------- +module Xanthous.Generators.UtilBench (benchmark, main) where +-------------------------------------------------------------------------------- +import Bench.Prelude +-------------------------------------------------------------------------------- +import Data.Array.IArray +import Data.Array.Unboxed +import System.Random (getStdGen) +-------------------------------------------------------------------------------- +import Xanthous.Generators.Util +import qualified Xanthous.Generators.CaveAutomata as CaveAutomata +import Xanthous.Data (Dimensions'(..)) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain [benchmark] + +-------------------------------------------------------------------------------- + +benchmark :: Benchmark +benchmark = bgroup "Generators.Util" + [ bgroup "floodFill" + [ env (NFWrapper <$> cells) $ \(NFWrapper ir) -> + bench "checkerboard" $ nf (floodFill ir) (1,0) + ] + ] + where + cells :: IO Cells + cells = CaveAutomata.generate + CaveAutomata.defaultParams + (Dimensions 50 50) + <$> getStdGen + +newtype NFWrapper a = NFWrapper a + +instance NFData (NFWrapper a) where + rnf (NFWrapper x) = x `seq` () diff --git a/users/grfn/xanthous/bench/Xanthous/RandomBench.hs b/users/grfn/xanthous/bench/Xanthous/RandomBench.hs new file mode 100644 index 000000000000..fae4af92a7a5 --- /dev/null +++ b/users/grfn/xanthous/bench/Xanthous/RandomBench.hs @@ -0,0 +1,32 @@ +-------------------------------------------------------------------------------- +module Xanthous.RandomBench (benchmark, main) where +-------------------------------------------------------------------------------- +import Bench.Prelude +-------------------------------------------------------------------------------- +import Control.Parallel.Strategies +import Control.Monad.Random +-------------------------------------------------------------------------------- +import Xanthous.Random +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain [benchmark] + +-------------------------------------------------------------------------------- + +benchmark :: Benchmark +benchmark = bgroup "Random" + [ bgroup "chooseSubset" + [ bench "serially" $ + nf (evalRand $ chooseSubset (0.5 :: Double) [1 :: Int ..1000000]) + (mkStdGen 1234) + ] + , bgroup "choose weightedBy" + [ bench "serially" $ + nf (evalRand + . choose + . weightedBy (\n -> product [n, pred n .. 1]) + $ [1 :: Int ..1000000]) + (mkStdGen 1234) + ] + ] diff --git a/users/grfn/xanthous/build/generic-arbitrary-export-garbitrary.patch b/users/grfn/xanthous/build/generic-arbitrary-export-garbitrary.patch new file mode 100644 index 000000000000..f0c936bfca18 --- /dev/null +++ b/users/grfn/xanthous/build/generic-arbitrary-export-garbitrary.patch @@ -0,0 +1,12 @@ +diff --git a/src/Test/QuickCheck/Arbitrary/Generic.hs b/src/Test/QuickCheck/Arbitrary/Generic.hs +index fed6ab3..91f59f1 100644 +--- a/src/Test/QuickCheck/Arbitrary/Generic.hs ++++ b/src/Test/QuickCheck/Arbitrary/Generic.hs +@@ -23,6 +23,7 @@ The generated 'arbitrary' method is equivalent to + + module Test.QuickCheck.Arbitrary.Generic + ( Arbitrary(..) ++ , GArbitrary + , genericArbitrary + , genericShrink + ) where diff --git a/users/grfn/xanthous/build/hgeometry-fix-haddock.patch b/users/grfn/xanthous/build/hgeometry-fix-haddock.patch new file mode 100644 index 000000000000..748c65b3e0db --- /dev/null +++ b/users/grfn/xanthous/build/hgeometry-fix-haddock.patch @@ -0,0 +1,13 @@ +diff --git a/src/Data/Geometry/PlanarSubdivision/Merge.hs b/src/Data/Geometry/PlanarSubdivision/Merge.hs +index 1136114..3f4e7bb 100644 +--- a/src/Data/Geometry/PlanarSubdivision/Merge.hs ++++ b/src/Data/Geometry/PlanarSubdivision/Merge.hs +@@ -153,7 +153,7 @@ mergeWith' mergeFaces p1 p2 = PlanarSubdivision cs vd rd rf + -- we have to shift the number of the *Arcs*. Since every dart + -- consists of two arcs, we have to shift by numDarts / 2 + -- Furthermore, we take numFaces - 1 since we want the first +- -- *internal* face of p2 (the one with FaceId 1) to correspond with the first free ++ -- /internal/ face of p2 (the one with FaceId 1) to correspond with the first free + -- position (at index numFaces) + + cs = p1^.components <> p2'^.components diff --git a/users/grfn/xanthous/build/update-comonad-extras.patch b/users/grfn/xanthous/build/update-comonad-extras.patch new file mode 100644 index 000000000000..cd1dbe24d361 --- /dev/null +++ b/users/grfn/xanthous/build/update-comonad-extras.patch @@ -0,0 +1,92 @@ +diff --git a/comonad-extras.cabal b/comonad-extras.cabal +index fc3745a..77a2f0d 100644 +--- a/comonad-extras.cabal ++++ b/comonad-extras.cabal +@@ -1,7 +1,7 @@ + name: comonad-extras + category: Control, Comonads +-version: 4.0 ++version: 5.0 + x-revision: 1 + license: BSD3 + cabal-version: >= 1.6 + license-file: LICENSE +@@ -34,8 +34,8 @@ library + build-depends: + array >= 0.3 && < 0.6, +- base >= 4 && < 4.7, +- containers >= 0.4 && < 0.6, +- comonad >= 4 && < 5, ++ base >= 4 && < 5, ++ containers >= 0.6 && < 0.7, ++ comonad >= 5 && < 6, + distributive >= 0.3.2 && < 1, +- semigroupoids >= 4 && < 5, +- transformers >= 0.2 && < 0.4 ++ semigroupoids >= 5 && < 6, ++ transformers >= 0.5 && < 0.6 + + exposed-modules: + Control.Comonad.Store.Zipper +diff --git a/src/Control/Comonad/Store/Pointer.hs b/src/Control/Comonad/Store/Pointer.hs +index 5044a1e..8d4c62d 100644 +--- a/src/Control/Comonad/Store/Pointer.hs ++++ b/src/Control/Comonad/Store/Pointer.hs +@@ -41,7 +41,6 @@ module Control.Comonad.Store.Pointer + , module Control.Comonad.Store.Class + ) where + +-import Control.Applicative + import Control.Comonad + import Control.Comonad.Hoist.Class + import Control.Comonad.Trans.Class +@@ -51,27 +50,8 @@ import Control.Comonad.Env.Class + import Data.Functor.Identity + import Data.Functor.Extend + import Data.Array +- + #ifdef __GLASGOW_HASKELL__ + import Data.Typeable +-instance (Typeable i, Typeable1 w) => Typeable1 (PointerT i w) where +- typeOf1 diwa = mkTyConApp storeTTyCon [typeOf (i diwa), typeOf1 (w diwa)] +- where +- i :: PointerT i w a -> i +- i = undefined +- w :: PointerT i w a -> w a +- w = undefined +- +-instance (Typeable i, Typeable1 w, Typeable a) => Typeable (PointerT i w a) where +- typeOf = typeOfDefault +- +-storeTTyCon :: TyCon +-#if __GLASGOW_HASKELL__ < 704 +-storeTTyCon = mkTyCon "Control.Comonad.Trans.Store.Pointer.PointerT" +-#else +-storeTTyCon = mkTyCon3 "comonad-extras" "Control.Comonad.Trans.Store.Pointer" "PointerT" +-#endif +-{-# NOINLINE storeTTyCon #-} + #endif + + type Pointer i = PointerT i Identity +@@ -83,6 +63,9 @@ runPointer :: Pointer i a -> (Array i a, i) + runPointer (PointerT (Identity f) i) = (f, i) + + data PointerT i w a = PointerT (w (Array i a)) i ++#ifdef __GLASGOW_HASKELL__ ++ deriving Typeable ++#endif + + runPointerT :: PointerT i w a -> (w (Array i a), i) + runPointerT (PointerT g i) = (g, i) +diff --git a/src/Control/Comonad/Store/Zipper.hs b/src/Control/Comonad/Store/Zipper.hs +index 3b70c86..decc378 100644 +--- a/src/Control/Comonad/Store/Zipper.hs ++++ b/src/Control/Comonad/Store/Zipper.hs +@@ -15,7 +15,6 @@ + module Control.Comonad.Store.Zipper + ( Zipper, zipper, zipper1, unzipper, size) where + +-import Control.Applicative + import Control.Comonad (Comonad(..)) + import Data.Functor.Extend + import Data.Foldable diff --git a/users/grfn/xanthous/default.nix b/users/grfn/xanthous/default.nix new file mode 100644 index 000000000000..c0eca446c9ed --- /dev/null +++ b/users/grfn/xanthous/default.nix @@ -0,0 +1,26 @@ +{ depot ? (import ../../../. {}) +, pkgs ? depot.third_party.nixpkgs +, ... }: + +let + ignore = depot.third_party.gitignoreSource.gitignoreFilter ./.; + src = builtins.path { + name = "xanthous-source"; + path = ./.; + filter = path: type: + !(type == "directory" && builtins.baseNameOf path == "server") + && !(type == "directory" && builtins.baseNameOf path == "docs") + && (ignore path type + || builtins.baseNameOf path == "package.yaml"); + }; + # generated by cabal2nix + basePkg = pkgs.haskellPackages.callPackage ./pkg.nix { }; +in + +pkgs.haskell.lib.overrideCabal basePkg (default: { + inherit src; + version = "canon"; + configureFlags = [ + "--ghc-option=-Wall --ghc-option=-Werror" + ] ++ (default.configureFlags or []); +}) diff --git a/users/grfn/xanthous/docs/raw-types.org b/users/grfn/xanthous/docs/raw-types.org new file mode 100644 index 000000000000..e5bcda04268f --- /dev/null +++ b/users/grfn/xanthous/docs/raw-types.org @@ -0,0 +1,24 @@ +#+TITLE: Raw Types (WIP) + + +* Raw Types +** Item +*** Attributes +| name | type | commentary | +|-----------------+---------------------------+------------------------------------------------------------------| +| name | string | | +| description | string | Not capitalized, should usually start with an indefinite article | +| longDescription | string | Capitalized, should usually start with an indefinite article | +| char | [[*EntityChar][EntityChar]] | | +| wieldable | [[*EntityWieldable][EntityWieldable]] | | +| density | number , [number, number] | Density, or range for random density, in g/mยณ | +| volume | number , [number, number] | Volume, or range for random volume, in mยณ | +* Data Types +** EntityChar +*** Attributes +| name | type | commentary | +|-------+------+-------------------------------------------------------| +| char | char | How the entity is displayed when dropped on the floor | +| style | Attr | | +** TODO EntityWieldable +** TODO Attr diff --git a/users/grfn/xanthous/hie.yaml b/users/grfn/xanthous/hie.yaml new file mode 100644 index 000000000000..e7cf01d158e5 --- /dev/null +++ b/users/grfn/xanthous/hie.yaml @@ -0,0 +1,10 @@ +cradle: + cabal: + - path: './src' + component: 'lib:xanthous' + - path: './test' + component: 'test:test' + - path: './app' + component: 'exe:xanthous' + - path: './bench' + component: 'bench:benchmark' diff --git a/users/grfn/xanthous/nixpkgs.nix b/users/grfn/xanthous/nixpkgs.nix new file mode 100644 index 000000000000..7d7c16440545 --- /dev/null +++ b/users/grfn/xanthous/nixpkgs.nix @@ -0,0 +1,3 @@ +args: +let pkgs = (import ../../../. args).third_party; +in pkgs // { inherit pkgs; } diff --git a/users/grfn/xanthous/package.yaml b/users/grfn/xanthous/package.yaml new file mode 100644 index 000000000000..630dc69c11d3 --- /dev/null +++ b/users/grfn/xanthous/package.yaml @@ -0,0 +1,156 @@ +name: xanthous +version: 0.1.0.0 +github: "glittershark/xanthous" +license: GPL-3 +author: "Griffin Smith" +maintainer: "root@gws.fyi" +copyright: "2019 Griffin Smith" + +extra-source-files: +- README.org + +synopsis: A WIP TUI RPG +category: Game + +description: Please see the README on GitHub at <https://github.com/glittershark/xanthous> + +dependencies: +- base + +- aeson +- array +- async +- QuickCheck +- quickcheck-text +- quickcheck-instances +- brick +- bifunctors +- checkers +- classy-prelude +- comonad +- comonad-extras +- constraints +- containers +- criterion +- data-default +- data-interval +- deepseq +- directory +- fgl +- fgl-arbitrary +- file-embed +- filepath +- generic-arbitrary +- generic-lens +- groups +- hgeometry +- hgeometry-combinatorial +- JuicyPixels +- lens +- lifted-async +- linear +- megaparsec +- mmorph +- monad-control +- MonadRandom +- mtl +- optparse-applicative +- parallel +- parser-combinators +- pointed +- random +- random-fu +- random-extras +- random-source +- raw-strings-qq +- reflection +- Rasterific +- splitmix +- streams +- stache +- semigroups +- semigroupoids +- tomland +- transformers +- text +- text-zipper +- vector +- vty +- witherable +- yaml +- zlib + +default-extensions: +- BlockArguments +- ConstraintKinds +- DataKinds +- DeriveAnyClass +- DeriveGeneric +- DerivingStrategies +- DerivingVia +- FlexibleContexts +- FlexibleInstances +- FunctionalDependencies +- GADTSyntax +- GeneralizedNewtypeDeriving +- KindSignatures +- StandaloneKindSignatures +- LambdaCase +- MultiWayIf +- NoImplicitPrelude +- NoStarIsType +- OverloadedStrings +- PolyKinds +- RankNTypes +- ScopedTypeVariables +- TupleSections +- TypeApplications +- TypeFamilies +- TypeOperators +- ViewPatterns + +ghc-options: +- -Wall + +library: + source-dirs: src + +executable: + source-dirs: app + main: Main.hs + dependencies: + - xanthous + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -O2 + +tests: + test: + main: Spec.hs + source-dirs: test + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -O0 + dependencies: + - xanthous + - tasty + - tasty-hunit + - tasty-quickcheck + - tasty-rerun + - lens-properties + +benchmarks: + benchmark: + main: Bench.hs + source-dirs: bench + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - xanthous + - criterion diff --git a/users/grfn/xanthous/pkg.nix b/users/grfn/xanthous/pkg.nix new file mode 100644 index 000000000000..0f0dbfc9822d --- /dev/null +++ b/users/grfn/xanthous/pkg.nix @@ -0,0 +1,80 @@ +{ mkDerivation, aeson, array, async, base, bifunctors, brick +, checkers, classy-prelude, comonad, comonad-extras, constraints +, containers, criterion, data-default, data-interval, deepseq +, directory, fgl, fgl-arbitrary, file-embed, filepath +, generic-arbitrary, generic-lens, groups, hgeometry +, hgeometry-combinatorial, hpack, JuicyPixels, lens +, lens-properties, lib, lifted-async, linear, megaparsec, mmorph +, monad-control, MonadRandom, mtl, optparse-applicative, parallel +, parser-combinators, pointed, QuickCheck, quickcheck-instances +, quickcheck-text, random, random-extras, random-fu, random-source +, Rasterific, raw-strings-qq, reflection, semigroupoids, semigroups +, splitmix, stache, streams, tasty, tasty-hunit, tasty-quickcheck +, tasty-rerun, text, text-zipper, tomland, transformers, vector +, vty, witherable, yaml, zlib +}: +mkDerivation { + pname = "xanthous"; + version = "0.1.0.0"; + src = ./.; + isLibrary = true; + isExecutable = true; + libraryHaskellDepends = [ + aeson array async base bifunctors brick checkers classy-prelude + comonad comonad-extras constraints containers criterion + data-default data-interval deepseq directory fgl fgl-arbitrary + file-embed filepath generic-arbitrary generic-lens groups hgeometry + hgeometry-combinatorial JuicyPixels lens lifted-async linear + megaparsec mmorph monad-control MonadRandom mtl + optparse-applicative parallel parser-combinators pointed QuickCheck + quickcheck-instances quickcheck-text random random-extras random-fu + random-source Rasterific raw-strings-qq reflection semigroupoids + semigroups splitmix stache streams text text-zipper tomland + transformers vector vty witherable yaml zlib + ]; + libraryToolDepends = [ hpack ]; + executableHaskellDepends = [ + aeson array async base bifunctors brick checkers classy-prelude + comonad comonad-extras constraints containers criterion + data-default data-interval deepseq directory fgl fgl-arbitrary + file-embed filepath generic-arbitrary generic-lens groups hgeometry + hgeometry-combinatorial JuicyPixels lens lifted-async linear + megaparsec mmorph monad-control MonadRandom mtl + optparse-applicative parallel parser-combinators pointed QuickCheck + quickcheck-instances quickcheck-text random random-extras random-fu + random-source Rasterific raw-strings-qq reflection semigroupoids + semigroups splitmix stache streams text text-zipper tomland + transformers vector vty witherable yaml zlib + ]; + testHaskellDepends = [ + aeson array async base bifunctors brick checkers classy-prelude + comonad comonad-extras constraints containers criterion + data-default data-interval deepseq directory fgl fgl-arbitrary + file-embed filepath generic-arbitrary generic-lens groups hgeometry + hgeometry-combinatorial JuicyPixels lens lens-properties + lifted-async linear megaparsec mmorph monad-control MonadRandom mtl + optparse-applicative parallel parser-combinators pointed QuickCheck + quickcheck-instances quickcheck-text random random-extras random-fu + random-source Rasterific raw-strings-qq reflection semigroupoids + semigroups splitmix stache streams tasty tasty-hunit + tasty-quickcheck tasty-rerun text text-zipper tomland transformers + vector vty witherable yaml zlib + ]; + benchmarkHaskellDepends = [ + aeson array async base bifunctors brick checkers classy-prelude + comonad comonad-extras constraints containers criterion + data-default data-interval deepseq directory fgl fgl-arbitrary + file-embed filepath generic-arbitrary generic-lens groups hgeometry + hgeometry-combinatorial JuicyPixels lens lifted-async linear + megaparsec mmorph monad-control MonadRandom mtl + optparse-applicative parallel parser-combinators pointed QuickCheck + quickcheck-instances quickcheck-text random random-extras random-fu + random-source Rasterific raw-strings-qq reflection semigroupoids + semigroups splitmix stache streams text text-zipper tomland + transformers vector vty witherable yaml zlib + ]; + prePatch = "hpack"; + homepage = "https://github.com/glittershark/xanthous#readme"; + description = "A WIP TUI RPG"; + license = lib.licenses.gpl3Only; +} diff --git a/users/grfn/xanthous/server/.envrc b/users/grfn/xanthous/server/.envrc new file mode 100644 index 000000000000..051d09d292a8 --- /dev/null +++ b/users/grfn/xanthous/server/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/users/grfn/xanthous/server/.gitignore b/users/grfn/xanthous/server/.gitignore new file mode 100644 index 000000000000..2f7896d1d136 --- /dev/null +++ b/users/grfn/xanthous/server/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/users/grfn/xanthous/server/Cargo.lock b/users/grfn/xanthous/server/Cargo.lock new file mode 100644 index 000000000000..46488d4575f2 --- /dev/null +++ b/users/grfn/xanthous/server/Cargo.lock @@ -0,0 +1,1937 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atomic-shim" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20fdac7156779a1a30d970e838195558b4810dd06aa69e7c7461bdc518edf9b" +dependencies = [ + "crossbeam", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64ct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c38c03b9506bd92bf1ef50665a81eda156f615438f7654bffba58907e6149d7" +dependencies = [ + "blowfish", + "crypto-mac", + "pbkdf2", + "sha2", + "zeroize", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blowfish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3ff3fc1de48c1ac2e3341c4df38b0d1bfb8fdf04632a187c8b75aaa319a7ab" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicase", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "color-eyre" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch 0.8.2", + "crossbeam-queue", + "crossbeam-utils 0.7.2", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch 0.8.2", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset 0.5.6", + "scopeguard", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.5", + "lazy_static", + "memoffset 0.6.4", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "cryptovec" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc7fa13a6bbb2322d325292c57f4c8e7291595506f8289968a0eb61c3130bdf" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-executor" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" + +[[package]] +name = "futures-macro" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-task" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "hyper" +version = "0.14.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00f42f354a2ed4894db863b3a4db47aef2d2e4435b937221749bd37a8a7aaa8" +dependencies = [ + "ahash", + "metrics-macros", + "proc-macro-hack", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343a5ceb38235928e7a5687412590f07e6d281522dcd9ff51246f8856eef5fe5" +dependencies = [ + "hyper", + "ipnet", + "metrics", + "metrics-util", + "parking_lot", + "quanta", + "thiserror", + "tokio", +] + +[[package]] +name = "metrics-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa72e4a3d157986dd2565c82ecbddcc23941513669a3766b938f6b72eb87f3f" +dependencies = [ + "lazy_static", + "proc-macro-hack", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "metrics-util" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c9b6aee519e1461b678952d3671652bb341d0664b1188f895a436a4e2e6ffa" +dependencies = [ + "ahash", + "aho-corasick", + "atomic-shim", + "crossbeam-epoch 0.9.5", + "crossbeam-utils 0.8.5", + "dashmap", + "hashbrown", + "indexmap", + "metrics", + "num_cpus", + "ordered-float", + "parking_lot", + "quanta", + "radix_trie", + "sketches-ddsketch", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset 0.6.4", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "ordered-float" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +dependencies = [ + "memchr", +] + +[[package]] +name = "owo-colors" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "password-hash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e0b28ace46c5a396546bcf443bf422b57049617433d8854227352a4a9b24e7" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "base64ct", + "crypto-mac", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quanta" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" +dependencies = [ + "crossbeam-utils 0.8.5", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "raw-cpuid" +version = "10.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929f54e29691d4e6a9cc558479de70db7aa3d98cd6fe7ab86d7507aa2886b9d2" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a77a8fd93886010f05e7ea0720e569d6d16c65329dbe3ec033bbbccccb017b" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "thrussh" +version = "0.33.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6540238a9adf83df6e66541c182a52acf892ab335595ca965c229ade8536f8" +dependencies = [ + "bitflags", + "byteorder", + "cryptovec", + "digest", + "flate2", + "futures", + "generic-array", + "log", + "rand", + "sha2", + "thiserror", + "thrussh-keys", + "thrussh-libsodium", + "tokio", +] + +[[package]] +name = "thrussh-keys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a72cc51a2932b18d92f7289332d8564cec4a5014063722a9d3fdca52c5d8f5ab" +dependencies = [ + "aes", + "bcrypt-pbkdf", + "bit-vec", + "block-modes", + "byteorder", + "cryptovec", + "data-encoding", + "dirs", + "futures", + "hmac", + "log", + "md5", + "num-bigint", + "num-integer", + "pbkdf2", + "rand", + "serde", + "serde_derive", + "sha2", + "thiserror", + "thrussh-libsodium", + "tokio", + "tokio-stream", + "yasna", +] + +[[package]] +name = "thrussh-libsodium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe89c70d27b1cb92e13bc8af63493e890d0de46dae4df0e28233f62b4ed9500" +dependencies = [ + "lazy_static", + "libc", + "libsodium-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "tokio" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xanthous-server" +version = "0.1.0" +dependencies = [ + "base64ct", + "clap", + "color-eyre", + "eyre", + "futures", + "libc", + "metrics", + "metrics-exporter-prometheus", + "nix", + "pbkdf2", + "tempfile", + "thrussh", + "thrussh-keys", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "bit-vec", + "num-bigint", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/users/grfn/xanthous/server/Cargo.toml b/users/grfn/xanthous/server/Cargo.toml new file mode 100644 index 000000000000..adb2a02391bd --- /dev/null +++ b/users/grfn/xanthous/server/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "xanthous-server" +version = "0.1.0" +edition = "2018" + +[dependencies] +clap = "3.0.0-beta.5" +color-eyre = "0.5.11" +eyre = "0.6.5" +thrussh = "0.33.5" +thrussh-keys = "0.21.0" +tracing = "0.1.29" +tracing-subscriber = "0.2.25" +metrics = "0.17.0" +metrics-exporter-prometheus = "0.6.1" +futures = "0.3.17" +libc = "0.2.103" +nix = "0.23.0" + +# Pins for rust 1.55 (2018 edition) until we have 1.56 in nixpkgs-unstable +pbkdf2 = "<0.9" +base64ct = "<1.2" + +[dependencies.tokio] +version = "1.13" +features = ["rt", "rt-multi-thread", "macros", "net", "process", "fs", "signal"] + +[dev-dependencies] +tempfile = "3.2.0" diff --git a/users/grfn/xanthous/server/default.nix b/users/grfn/xanthous/server/default.nix new file mode 100644 index 000000000000..0b3900e4d5fe --- /dev/null +++ b/users/grfn/xanthous/server/default.nix @@ -0,0 +1,14 @@ +args@{ + depot ? import ../../../.. {} +, pkgs ? depot.third_party.nixpkgs +, ... +}: + +depot.third_party.naersk.buildPackage { + name = "xanthous-server"; + version = "0.0.1"; + src = depot.third_party.gitignoreSource ./.; + passthru = { + docker = import ./docker.nix args; + }; +} diff --git a/users/grfn/xanthous/server/docker.nix b/users/grfn/xanthous/server/docker.nix new file mode 100644 index 000000000000..a62943c2b077 --- /dev/null +++ b/users/grfn/xanthous/server/docker.nix @@ -0,0 +1,19 @@ +{ depot ? import ../../../.. {} +, pkgs ? depot.third_party.nixpkgs +, ... +}: + +let + inherit (depot.users.grfn) xanthous; + xanthous-server = xanthous.server; +in pkgs.dockerTools.buildLayeredImage { + name = "xanthous-server"; + tag = "latest"; + contents = [ xanthous xanthous-server ]; + config = { + Cmd = [ + "${xanthous-server}/bin/xanthous-server" + "--xanthous-binary-path" "${xanthous}/bin/xanthous" + ]; + }; +} diff --git a/users/grfn/xanthous/server/module.nix b/users/grfn/xanthous/server/module.nix new file mode 100644 index 000000000000..73ac276caf4a --- /dev/null +++ b/users/grfn/xanthous/server/module.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, depot, ... }: + +let + cfg = config.services.xanthous-server; +in { + options = with lib; { + services.xanthous-server = { + enable = mkEnableOption "xanthous server"; + + port = mkOption { + type = types.int; + default = 2222; + description = "Port to listen to for SSH connections"; + }; + + metricsPort = mkOption { + type = types.int; + default = 9000; + description = "Port to listen to for prometheus metrics"; + }; + + image = mkOption { + type = types.package; + default = depot.users.grfn.xanthous.server.docker; + description = "OCI image file to run"; + }; + + ed25519SecretKeyFile = mkOption { + type = with types; uniq string; + description = "Path to the ed25519 secret key for the server"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers."xanthous-server" = { + autoStart = true; + image = "${cfg.image.imageName}:${cfg.image.imageTag}"; + imageFile = cfg.image; + ports = [ + "${toString cfg.port}:22" + "${toString cfg.metricsPort}:9000" + ]; + environment.SECRET_KEY_FILE = "/secret-key"; + volumes = [ "/etc/secrets/xanthous-server-secret-key:/secret-key" ]; + }; + }; +} diff --git a/users/grfn/xanthous/server/shell.nix b/users/grfn/xanthous/server/shell.nix new file mode 100644 index 000000000000..a6747175f105 --- /dev/null +++ b/users/grfn/xanthous/server/shell.nix @@ -0,0 +1,11 @@ +let + depot = import ../../../.. {}; + pkgs = depot.third_party.nixpkgs; +in + +pkgs.mkShell { + buildInputs = with pkgs; [ + rustup + rust-analyzer + ]; +} diff --git a/users/grfn/xanthous/server/src/main.rs b/users/grfn/xanthous/server/src/main.rs new file mode 100644 index 000000000000..ed8f831c7d3f --- /dev/null +++ b/users/grfn/xanthous/server/src/main.rs @@ -0,0 +1,388 @@ +use std::net::SocketAddr; +use std::path::PathBuf; +use std::pin::Pin; +use std::process::Command; +use std::str; +use std::sync::Arc; + +use clap::Parser; +use color_eyre::eyre::Result; +use eyre::{bail, Context}; +use futures::future::{ready, Ready}; +use futures::Future; +use metrics_exporter_prometheus::PrometheusBuilder; +use nix::pty::Winsize; +use pty::ChildHandle; +use thrussh::ChannelId; +use thrussh::{ + server::{self, Auth, Session}, + CryptoVec, +}; +use thrussh_keys::decode_secret_key; +use thrussh_keys::key::KeyPair; +use tokio::fs::File; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; +use tokio::select; +use tokio::time::Instant; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; +use tracing_subscriber::EnvFilter; + +use crate::pty::WaitPid; + +mod metrics; +mod pty; + +use crate::metrics::reported::*; +use crate::metrics::{decrement_gauge, histogram, increment_counter, increment_gauge}; + +/// SSH-compatible server for playing Xanthous +#[derive(Parser, Debug)] +struct Opts { + /// Address to bind to + #[clap(long, short = 'a', default_value = "0.0.0.0:22")] + address: String, + + /// Address to listen to for metrics + #[clap(long, default_value = "0.0.0.0:9000")] + metrics_address: SocketAddr, + + /// Format to use when emitting log events + #[clap( + long, + env = "LOG_FORMAT", + default_value = "full", + possible_values = &["compact", "full", "pretty", "json"] + )] + log_format: String, + + /// Full path to the xanthous binary + #[clap(long, env = "XANTHOUS_BINARY_PATH")] + xanthous_binary_path: String, + + /// Path to a file containing the ed25519 secret key for the server + #[clap(long, env = "SECRET_KEY_FILE")] + secret_key_file: PathBuf, + + /// Level to log at + #[clap(long, env = "LOG_LEVEL", default_value = "info")] + log_level: String, +} + +impl Opts { + async fn read_secret_key(&self) -> Result<KeyPair> { + let mut file = File::open(&self.secret_key_file) + .await + .context("Reading secret key file")?; + let mut secret_key = Vec::with_capacity(464); + file.read_to_end(&mut secret_key).await?; + Ok(decode_secret_key(str::from_utf8(&secret_key)?, None)?) + } + + async fn ssh_server_config(&self) -> Result<server::Config> { + let key_pair = self.read_secret_key().await?; + + Ok(server::Config { + server_id: "SSH-2.0-xanthous".to_owned(), + keys: vec![key_pair], + ..Default::default() + }) + } + + fn init_logging(&self) -> Result<()> { + let filter = EnvFilter::try_new(&self.log_level)?; + let s = tracing_subscriber::fmt().with_env_filter(filter); + + match self.log_format.as_str() { + "compact" => s.compact().init(), + "full" => s.init(), + "pretty" => s.pretty().init(), + "json" => s.json().with_current_span(true).init(), + _ => bail!("Invalid log format `{}`"), + } + + Ok(()) + } +} + +struct Handler { + address: SocketAddr, + xanthous_binary_path: &'static str, + username: Option<String>, + child: Option<ChildHandle>, +} + +async fn run_child( + mut child: pty::Child, + mut server_handle: server::Handle, + channel_id: ChannelId, +) -> Result<()> { + let mut buf = [0; 2048]; + loop { + select! { + r = child.tty.read(&mut buf) => { + let read_bytes = r?; + if read_bytes == 0 { + info!("EOF received from process"); + let _ = server_handle.close(channel_id).await; + return Ok(()) + } else { + trace!(?read_bytes, "read bytes from child"); + let _ = server_handle.data(channel_id, CryptoVec::from_slice(&buf[..read_bytes])).await; + } + } + status = WaitPid::new(child.pid) => { + match status { + Ok(_status) => info!("Child exited"), + Err(error) => error!(%error, "Child failed"), + } + let _ = server_handle.close(channel_id).await; + return Ok(()) + } + } + } +} + +impl Handler { + async fn spawn_shell( + &mut self, + mut handle: server::Handle, + channel_id: ChannelId, + term: String, + winsize: Winsize, + ) -> Result<()> { + let mut cmd = Command::new(self.xanthous_binary_path); + cmd.env("TERM", term); + if let Some(username) = &self.username { + cmd.args(["--name", username]); + } + cmd.arg("--disable-saving"); + + let child = pty::spawn(cmd, Some(winsize), None).await?; + info!(pid = %child.pid, "Spawned child"); + increment_gauge!(RUNNING_PROCESSES, 1.0); + self.child = Some(child.handle().await?); + tokio::spawn( + async move { + let span = info_span!("child", pid = %child.pid); + if let Err(error) = run_child(child, handle.clone(), channel_id) + .instrument(span.clone()) + .await + { + span.in_scope(|| error!(%error, "Error running child")); + let _ = handle.close(channel_id).await; + } + decrement_gauge!(RUNNING_PROCESSES, 1.0); + } + .in_current_span(), + ); + Ok(()) + } +} + +#[allow(clippy::type_complexity)] +impl server::Handler for Handler { + type Error = eyre::Error; + type FutureAuth = Ready<Result<(Self, Auth)>>; + type FutureUnit = Pin<Box<dyn Future<Output = Result<(Self, Session)>> + Send + 'static>>; + type FutureBool = Ready<Result<(Self, Session, bool)>>; + + fn finished_auth(self, auth: Auth) -> Self::FutureAuth { + ready(Ok((self, auth))) + } + + fn finished_bool(self, b: bool, session: Session) -> Self::FutureBool { + ready(Ok((self, session, b))) + } + + fn finished(self, session: Session) -> Self::FutureUnit { + Box::pin(ready(Ok((self, session)))) + } + + fn auth_none(mut self, username: &str) -> Self::FutureAuth { + info!(%username, "Accepted new connection"); + self.username = Some(username.to_owned()); + self.finished_auth(Auth::Accept) + } + + fn auth_password(mut self, username: &str, _password: &str) -> Self::FutureAuth { + info!(%username, "Accepted new connection"); + self.username = Some(username.to_owned()); + self.finished_auth(Auth::Accept) + } + + fn auth_publickey( + mut self, + username: &str, + _: &thrussh_keys::key::PublicKey, + ) -> Self::FutureAuth { + info!(%username, "Accepted new connection"); + self.username = Some(username.to_owned()); + self.finished_auth(Auth::Accept) + } + + fn pty_request( + mut self, + channel: thrussh::ChannelId, + term: &str, + col_width: u32, + row_height: u32, + pix_width: u32, + pix_height: u32, + modes: &[(thrussh::Pty, u32)], + session: Session, + ) -> Self::FutureUnit { + let term = term.to_owned(); + let modes = modes.to_vec(); + Box::pin(async move { + debug!( + %term, + %col_width, + %row_height, + %pix_width, + %pix_height, + ?modes, + "PTY Requested" + ); + + self.spawn_shell( + session.handle(), + channel, + term, + Winsize { + ws_row: row_height as _, + ws_col: col_width as _, + ws_xpixel: pix_width as _, + ws_ypixel: pix_height as _, + }, + ) + .await?; + + Ok((self, session)) + }) + } + + fn window_change_request( + mut self, + _channel: ChannelId, + col_width: u32, + row_height: u32, + pix_width: u32, + pix_height: u32, + session: Session, + ) -> Self::FutureUnit { + Box::pin(async move { + if let Some(child) = self.child.as_mut() { + trace!(%row_height, %col_width, "Window resize request received"); + child + .resize_window(Winsize { + ws_row: row_height as _, + ws_col: col_width as _, + ws_xpixel: pix_width as _, + ws_ypixel: pix_height as _, + }) + .await?; + } else { + warn!("Resize request received without child process; ignoring"); + } + + Ok((self, session)) + }) + } + + fn data( + mut self, + _channel: thrussh::ChannelId, + data: &[u8], + session: Session, + ) -> Self::FutureUnit { + trace!(data = %String::from_utf8_lossy(data), raw_data = ?data); + let data = data.to_owned(); + Box::pin(async move { + if let Some(child) = self.child.as_mut() { + child.write_all(&data).await?; + } else { + warn!("Data received without child process; ignoring"); + } + + Ok((self, session)) + }) + } +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install()?; + let opts = Box::leak::<'static>(Box::new(Opts::parse())); + opts.init_logging()?; + PrometheusBuilder::new() + .listen_address(opts.metrics_address) + .install()?; + metrics::register(); + + let config = Arc::new(opts.ssh_server_config().await?); + info!(address = %opts.address, "Listening for new SSH connections"); + let listener = TcpListener::bind(&opts.address).await?; + + loop { + let (stream, address) = listener.accept().await?; + increment_counter!(CONNECTIONS_ACCEPTED); + increment_gauge!(ACTIVE_CONNECTIONS, 1.0); + let config = config.clone(); + let handler = Handler { + xanthous_binary_path: &opts.xanthous_binary_path, + address, + username: None, + child: None, + }; + tokio::spawn(async move { + let span = info_span!("client", address = %handler.address); + let start = Instant::now(); + if let Err(error) = server::run_stream(config, stream, handler) + .instrument(span.clone()) + .await + { + span.in_scope(|| error!(%error)); + } + let duration = start.elapsed(); + span.in_scope(|| info!(duration_ms = %duration.as_millis(), "Client disconnected")); + histogram!(CONNECTION_DURATION, duration); + decrement_gauge!(ACTIVE_CONNECTIONS, 1.0); + }); + } +} + +#[cfg(test)] +mod tests { + use tempfile::NamedTempFile; + + use super::*; + + #[tokio::test] + async fn read_secret_key() { + use std::io::Write; + + let mut file = NamedTempFile::new().unwrap(); + file.write_all( + b" +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAYz80xcK7jYxZMAl6apIHKRtB0Z2U78gG39c1QaIhgMwAAAJB9vxK9fb8S +vQAAAAtzc2gtZWQyNTUxOQAAACAYz80xcK7jYxZMAl6apIHKRtB0Z2U78gG39c1QaIhgMw +AAAEDNZ0d3lLNBGU6Im4JOpr490TOjm+cB7kMVXjVg3iCowBjPzTFwruNjFkwCXpqkgcpG +0HRnZTvyAbf1zVBoiGAzAAAACHRlc3Qta2V5AQIDBAU= +-----END OPENSSH PRIVATE KEY----- +", + ) + .unwrap(); + + let opts: Opts = Opts::parse_from(&[ + "xanthous-server".as_ref(), + "--xanthous-binary-path".as_ref(), + "/bin/xanthous".as_ref(), + "--secret-key-file".as_ref(), + file.path().as_os_str(), + ]); + opts.read_secret_key().await.unwrap(); + } +} diff --git a/users/grfn/xanthous/server/src/metrics.rs b/users/grfn/xanthous/server/src/metrics.rs new file mode 100644 index 000000000000..6912cdd9c9ee --- /dev/null +++ b/users/grfn/xanthous/server/src/metrics.rs @@ -0,0 +1,24 @@ +pub use ::metrics::*; + +pub mod reported { + /// Counter: Connections accepted on the TCP listener + pub const CONNECTIONS_ACCEPTED: &str = "ssh.connections.accepted"; + + /// Histogram: Connection duration + pub const CONNECTION_DURATION: &str = "ssh.connections.duration"; + + /// Gauge: Currently active connections + pub const ACTIVE_CONNECTIONS: &str = "ssh.connections.active"; + + /// Gauge: Currently running xanthous processes + pub const RUNNING_PROCESSES: &str = "ssh.child.processes"; +} + +pub fn register() { + use reported::*; + + register_counter!(CONNECTIONS_ACCEPTED); + register_histogram!(CONNECTION_DURATION); + register_gauge!(ACTIVE_CONNECTIONS); + register_gauge!(RUNNING_PROCESSES); +} diff --git a/users/grfn/xanthous/server/src/pty.rs b/users/grfn/xanthous/server/src/pty.rs new file mode 100644 index 000000000000..611130f5bcd9 --- /dev/null +++ b/users/grfn/xanthous/server/src/pty.rs @@ -0,0 +1,173 @@ +use std::io::{self}; +use std::os::unix::prelude::{AsRawFd, CommandExt, FromRawFd}; +use std::pin::Pin; +use std::process::{abort, Command}; +use std::task::{Context, Poll}; + +use eyre::{bail, Result}; +use futures::Future; +use nix::pty::forkpty; +use nix::pty::Winsize; +use nix::sys::termios::Termios; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use nix::unistd::{ForkResult, Pid}; +use tokio::fs::File; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::signal::unix::{signal, Signal, SignalKind}; +use tokio::task::spawn_blocking; + +mod ioctl { + use super::Winsize; + use libc::TIOCSWINSZ; + use nix::ioctl_write_ptr_bad; + + ioctl_write_ptr_bad!(tiocswinsz, TIOCSWINSZ, Winsize); +} + +async fn asyncify<F, T>(f: F) -> Result<T> +where + F: FnOnce() -> Result<T> + Send + 'static, + T: Send + 'static, +{ + match spawn_blocking(f).await { + Ok(res) => res, + Err(_) => bail!("background task failed",), + } +} + +pub struct Child { + pub tty: File, + pub pid: Pid, +} + +pub struct ChildHandle { + pub tty: File, +} + +pub struct WaitPid { + pid: Pid, + signal: Signal, +} + +impl WaitPid { + pub fn new(pid: Pid) -> Self { + Self { + pid, + signal: signal(SignalKind::child()).unwrap(), + } + } +} + +impl Future for WaitPid { + type Output = nix::Result<WaitStatus>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + let _ = self.signal.poll_recv(cx); + match waitpid(self.pid, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::StillAlive) => Poll::Pending, + result => Poll::Ready(result), + } + } +} + +impl Child { + pub async fn handle(&self) -> io::Result<ChildHandle> { + Ok(ChildHandle { + tty: self.tty.try_clone().await?, + }) + } +} + +impl ChildHandle { + pub async fn resize_window(&mut self, winsize: Winsize) -> Result<()> { + let fd = self.tty.as_raw_fd(); + asyncify(move || unsafe { + ioctl::tiocswinsz(fd, &winsize as *const Winsize)?; + Ok(()) + }) + .await + } +} + +pub async fn spawn( + mut cmd: Command, + winsize: Option<Winsize>, + termios: Option<Termios>, +) -> Result<Child> { + asyncify(move || unsafe { + let res = forkpty(winsize.as_ref(), termios.as_ref())?; + match res.fork_result { + ForkResult::Parent { child } => Ok(Child { + pid: child, + tty: File::from_raw_fd(res.master), + }), + ForkResult::Child => { + cmd.exec(); + abort(); + } + } + }) + .await +} + +impl AsyncRead for Child { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll<io::Result<()>> { + Pin::new(&mut self.tty).poll_read(cx, buf) + } +} + +impl AsyncWrite for Child { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll<Result<usize, io::Error>> { + Pin::new(&mut self.tty).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.tty).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.tty).poll_shutdown(cx) + } +} + +impl AsyncRead for ChildHandle { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll<io::Result<()>> { + Pin::new(&mut self.tty).poll_read(cx, buf) + } +} + +impl AsyncWrite for ChildHandle { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll<Result<usize, io::Error>> { + Pin::new(&mut self.tty).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.tty).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Result<(), io::Error>> { + Pin::new(&mut self.tty).poll_shutdown(cx) + } +} diff --git a/users/grfn/xanthous/shell.nix b/users/grfn/xanthous/shell.nix new file mode 100644 index 000000000000..572ed211bcf4 --- /dev/null +++ b/users/grfn/xanthous/shell.nix @@ -0,0 +1,23 @@ +let + depot = import ../../../. {}; + inherit (depot) third_party; + pkgs = third_party.nixpkgs; +in + +(pkgs.haskellPackages.extend (pkgs.haskell.lib.packageSourceOverrides { + xanthous = third_party.gitignoreSource ./.; +})).shellFor { + packages = p: [p.xanthous]; + withHoogle = true; + doBenchmark = true; + buildInputs = (with pkgs.haskellPackages; [ + cabal-install + ghc-prof-flamegraph + hp2pretty + hlint + haskell-language-server + cabal2nix + ]) ++ (with pkgs; [ + qpdf + ]); +} diff --git a/users/grfn/xanthous/src/Data/Aeson/Generic/DerivingVia.hs b/users/grfn/xanthous/src/Data/Aeson/Generic/DerivingVia.hs new file mode 100644 index 000000000000..e89fcd621157 --- /dev/null +++ b/users/grfn/xanthous/src/Data/Aeson/Generic/DerivingVia.hs @@ -0,0 +1,168 @@ +{-# LANGUAGE ConstraintKinds, DataKinds, DeriveGeneric, DerivingVia #-} +{-# LANGUAGE ExplicitNamespaces, FlexibleContexts, FlexibleInstances #-} +{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds, ScopedTypeVariables, StandaloneDeriving #-} +{-# LANGUAGE TypeApplications, TypeFamilies, TypeInType, TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wall #-} +-- | https://gist.github.com/konn/27c00f784dd883ec2b90eab8bc84a81d +module Data.Aeson.Generic.DerivingVia + ( StrFun(..), Setting(..), SumEncoding'(..), DefaultOptions, WithOptions(..) + , -- Utility type synonyms to save ticks (') before promoted data constructors + type Drop, type CamelTo2, type UserDefined + , type TaggedObj, type UntaggedVal, type ObjWithSingleField, type TwoElemArr + , type FieldLabelModifier + , type ConstructorTagModifier + , type AllNullaryToStringTag + , type OmitNothingFields + , type SumEnc + , type UnwrapUnaryRecords + , type TagSingleConstructors + ) + where + +import Prelude +import Data.Aeson (FromJSON (..), GFromJSON, GToJSON, + ToJSON (..)) +import Data.Aeson (Options (..), Zero, camelTo2, + genericParseJSON) +import Data.Aeson (defaultOptions, genericToJSON) +import qualified Data.Aeson as Aeson +import Data.Kind (Constraint, Type) +import Data.Proxy (Proxy (..)) +import Data.Reflection (Reifies (..)) +import GHC.Generics (Generic, Rep) +import GHC.TypeLits (KnownNat, KnownSymbol, natVal, symbolVal) +import GHC.TypeLits (Nat, Symbol) + +newtype WithOptions options a = WithOptions { runWithOptions :: a } + +data StrFun = Drop Nat + | CamelTo2 Symbol + | forall p. UserDefined p + +type Drop = 'Drop +type CamelTo2 = 'CamelTo2 +type UserDefined = 'UserDefined + +type family Demoted a where + Demoted Symbol = String + Demoted StrFun = String -> String + Demoted [a] = [Demoted a] + Demoted Setting = Options -> Options + Demoted SumEncoding' = Aeson.SumEncoding + Demoted a = a + +data SumEncoding' = TaggedObj {tagFieldName' :: Symbol, contentsFieldName :: Symbol } + | UntaggedVal + | ObjWithSingleField + | TwoElemArr + +type TaggedObj = 'TaggedObj +type UntaggedVal = 'UntaggedVal +type ObjWithSingleField = 'ObjWithSingleField +type TwoElemArr = 'TwoElemArr + +data Setting = FieldLabelModifier [StrFun] + | ConstructorTagModifier [StrFun] + | AllNullaryToStringTag Bool + | OmitNothingFields Bool + | SumEnc SumEncoding' + | UnwrapUnaryRecords Bool + | TagSingleConstructors Bool + +type FieldLabelModifier = 'FieldLabelModifier +type ConstructorTagModifier = 'ConstructorTagModifier +-- | If 'True' the constructors of a datatype, with all nullary constructors, +-- will be encoded to just a string with the constructor tag. If 'False' the +-- encoding will always follow the 'SumEncoding'. +type AllNullaryToStringTag = 'AllNullaryToStringTag +type OmitNothingFields = 'OmitNothingFields +type SumEnc = 'SumEnc +-- | Hide the field name when a record constructor has only one field, like a +-- newtype. +type UnwrapUnaryRecords = 'UnwrapUnaryRecords +-- | Encode types with a single constructor as sums, so that +-- 'AllNullaryToStringTag' and 'SumEncoding' apply. +type TagSingleConstructors = 'TagSingleConstructors + +class Demotable (a :: k) where + demote :: proxy a -> Demoted k + +type All :: (Type -> Constraint) -> [Type] -> Constraint +type family All p xs where + All p '[] = () + All p (x ': xs) = (p x, All p xs) + +instance Reifies f (String -> String) => Demotable ('UserDefined f) where + demote _ = reflect @f Proxy + +instance KnownSymbol sym => Demotable sym where + demote = symbolVal + +instance (KnownSymbol s, KnownSymbol t) => Demotable ('TaggedObj s t) where + demote _ = Aeson.TaggedObject (symbolVal @s Proxy) (symbolVal @t Proxy) + +instance Demotable 'UntaggedVal where + demote _ = Aeson.UntaggedValue + +instance Demotable 'ObjWithSingleField where + demote _ = Aeson.ObjectWithSingleField + +instance Demotable 'TwoElemArr where + demote _ = Aeson.TwoElemArray + +instance Demotable xs => Demotable ('FieldLabelModifier xs) where + demote _ o = o { fieldLabelModifier = foldr (.) id (demote (Proxy @xs)) } + +instance Demotable xs => Demotable ('ConstructorTagModifier xs) where + demote _ o = o { constructorTagModifier = foldr (.) id (demote (Proxy @xs)) } + +instance Demotable b => Demotable ('AllNullaryToStringTag b) where + demote _ o = o { allNullaryToStringTag = demote (Proxy @b) } + +instance Demotable b => Demotable ('OmitNothingFields b) where + demote _ o = o { omitNothingFields = demote (Proxy @b) } + +instance Demotable b => Demotable ('UnwrapUnaryRecords b) where + demote _ o = o { unwrapUnaryRecords = demote (Proxy @b) } + +instance Demotable b => Demotable ('TagSingleConstructors b) where + demote _ o = o { tagSingleConstructors = demote (Proxy @b) } + +instance Demotable b => Demotable ('SumEnc b) where + demote _ o = o { sumEncoding = demote (Proxy @b) } + +instance Demotable 'True where + demote _ = True + +instance Demotable 'False where + demote _ = False + +instance KnownNat n => Demotable ('Drop n) where + demote _ = drop (fromIntegral $ natVal (Proxy :: Proxy n)) + +instance KnownSymbol sym => Demotable ('CamelTo2 sym) where + demote _ = camelTo2 $ head $ symbolVal @sym Proxy + +instance {-# OVERLAPPING #-} Demotable ('[] :: [k]) where + demote _ = [] + +instance (Demotable (x :: k), Demotable (xs :: [k])) => Demotable (x ': xs) where + demote _ = demote (Proxy @x) : demote (Proxy @xs) + +type DefaultOptions = ('[] :: [Setting]) + +reflectOptions :: forall xs proxy. Demotable (xs :: [Setting]) => proxy xs -> Options +reflectOptions pxy = foldr (.) id (demote pxy) defaultOptions + +instance (Demotable (options :: [Setting])) => Reifies options Options where + reflect = reflectOptions + +instance (Generic a, GToJSON Zero (Rep a), Reifies (options :: k) Options) + => ToJSON (WithOptions options a) where + toJSON = genericToJSON (reflect (Proxy @options)) . runWithOptions + +instance (Generic a, GFromJSON Zero (Rep a), Reifies (options :: k) Options) + => FromJSON (WithOptions options a) where + parseJSON = fmap WithOptions . genericParseJSON (reflect (Proxy @options)) diff --git a/users/grfn/xanthous/src/Xanthous/AI/Gormlak.hs b/users/grfn/xanthous/src/Xanthous/AI/Gormlak.hs new file mode 100644 index 000000000000..1f2b513ffe0e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/AI/Gormlak.hs @@ -0,0 +1,201 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE UndecidableInstances #-} +-------------------------------------------------------------------------------- +module Xanthous.AI.Gormlak + ( HasVisionRadius(..) + , GormlakBrain(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (lines) +-------------------------------------------------------------------------------- +import Control.Monad.State +import Control.Monad.Random +import Data.Aeson (object) +import qualified Data.Aeson as A +import Data.Generics.Product.Fields +-------------------------------------------------------------------------------- +import Xanthous.Data + ( Positioned(..), positioned, position, _Position + , diffPositions, stepTowards, isUnit + , Ticks, (|*|), invertedRate + ) +import Xanthous.Data.EntityMap +import Xanthous.Entities.Creature.Hippocampus +import Xanthous.Entities.Character (Character) +import qualified Xanthous.Entities.Character as Character +import qualified Xanthous.Entities.RawTypes as Raw +import Xanthous.Entities.RawTypes + ( CreatureType, HasLanguage(language), getLanguage + , HasAttacks (attacks), creatureAttackMessage + ) +import Xanthous.Entities.Common + ( wielded, Inventory, wieldedItems, WieldedItem (WieldedItem) ) +import Xanthous.Game.State +import Xanthous.Game.Lenses + ( entitiesCollision, collisionAt + , character, characterPosition, positionIsCharacterVisible + , hearingRadius + ) +import Xanthous.Data.EntityMap.Graphics (linesOfSight, canSee) +import Xanthous.Random +import Xanthous.Monad (say, message) +import Xanthous.Generators.Speech (word) +import qualified Linear.Metric as Metric +import qualified Xanthous.Messages as Messages +-------------------------------------------------------------------------------- + +-- TODO move the following two classes to a more central location + +class HasVisionRadius a where visionRadius :: a -> Word + +type IsCreature entity = + ( HasVisionRadius entity + , HasField "_hippocampus" entity entity Hippocampus Hippocampus + , HasField "_creatureType" entity entity CreatureType CreatureType + , HasField "_inventory" entity entity Inventory Inventory + , A.ToJSON entity + ) + +-------------------------------------------------------------------------------- + +stepGormlak + :: forall entity m. + ( MonadState GameState m, MonadRandom m + , IsCreature entity + ) + => Ticks + -> Positioned entity + -> m (Positioned entity) +stepGormlak ticks pe@(Positioned pos creature) = do + canSeeCharacter <- uses entities $ canSee (entityIs @Character) pos vision + + let selectDestination pos' creature' = destinationFromPos <$> do + if canSeeCharacter + then do + charPos <- use characterPosition + if isUnit (pos' `diffPositions` charPos) + then attackCharacter $> pos' + else pure $ pos' `stepTowards` charPos + else do + lines <- map (takeWhile (isNothing . entitiesCollision . map snd . snd) + -- the first item on these lines is always the creature itself + . fromMaybe mempty . tailMay) + . linesOfSight pos' (visionRadius creature') + <$> use entities + line <- choose $ weightedBy length lines + pure $ fromMaybe pos' $ fmap fst . headMay =<< line + + pe' <- if canSeeCharacter && not (creature ^. creatureGreeted) + then yellAtCharacter $> (pe & positioned . creatureGreeted .~ True) + else pure pe + + dest <- maybe (selectDestination pos creature) pure + . mfilter (\(Destination p _) -> p /= pos) + $ creature ^. hippocampus . destination + let progress' = + dest ^. destinationProgress + + creature ^. creatureType . Raw.speed . invertedRate |*| ticks + if progress' < 1 + then pure + $ pe' + & positioned . hippocampus . destination + ?~ (dest & destinationProgress .~ progress') + else do + let newPos = dest ^. destinationPosition + remainingSpeed = progress' - 1 + newDest <- selectDestination newPos creature + <&> destinationProgress +~ remainingSpeed + let pe'' = pe' & positioned . hippocampus . destination ?~ newDest + collisionAt newPos >>= \case + Nothing -> pure $ pe'' & position .~ newPos + Just Stop -> pure pe'' + Just Combat -> do + ents <- use $ entities . atPosition newPos + when (any (entityIs @Character) ents) attackCharacter + pure pe' + where + vision = visionRadius creature + attackCharacter = do + dmg <- case creature ^? inventory . wielded . wieldedItems of + Just (WieldedItem item wi) -> do + let msg = fromMaybe + (Messages.lookup ["combat", "creatureAttack", "genericWeapon"]) + $ wi ^. creatureAttackMessage + message msg $ object [ "creature" A..= creature + , "item" A..= item + ] + pure $ wi ^. Raw.damage + Nothing -> do + attack <- choose $ creature ^. creatureType . attacks + attackDescription <- Messages.render (attack ^. Raw.description) + $ object [] + say ["combat", "creatureAttack", "natural"] + $ object [ "creature" A..= creature + , "attackDescription" A..= attackDescription + ] + pure $ attack ^. Raw.damage + + character %= Character.damage dmg + + yellAtCharacter = for_ (creature ^. creatureType . language) + $ \lang -> do + utterance <- fmap (<> "!") . word $ getLanguage lang + creatureSaysText pe utterance + + creatureGreeted :: Lens' entity Bool + creatureGreeted = hippocampus . greetedCharacter + + +-- | A creature sends some text +-- +-- If that creature is visible to the character, its description will be +-- included, otherwise if it's within earshot the character will just hear the +-- sound +creatureSaysText + :: (MonadState GameState m, MonadRandom m, IsCreature entity) + => Positioned entity + -> Text + -> m () +creatureSaysText ent txt = do + let entPos = ent ^. position . _Position . to (fmap fromIntegral) + charPos <- use $ characterPosition . _Position . to (fmap fromIntegral) + let dist :: Int + dist = round $ Metric.distance @_ @Double entPos charPos + audible = dist <= fromIntegral hearingRadius + when audible $ do + visible <- positionIsCharacterVisible $ ent ^. position + let path = ["entities", "say", "creature"] + <> [if visible then "visible" else "invisible"] + params = object [ "creature" A..= (ent ^. positioned) + , "message" A..= txt + ] + say path params + +newtype GormlakBrain entity = GormlakBrain { _unGormlakBrain :: entity } + +instance (IsCreature entity) => Brain (GormlakBrain entity) where + step ticks + = fmap (fmap GormlakBrain) + . stepGormlak ticks + . fmap _unGormlakBrain + entityCanMove = const True + +hippocampus :: HasField "_hippocampus" s t a b => Lens s t a b +hippocampus = field @"_hippocampus" + +creatureType :: HasField "_creatureType" s t a b => Lens s t a b +creatureType = field @"_creatureType" + +inventory :: HasField "_inventory" s t a b => Lens s t a b +inventory = field @"_inventory" + +-------------------------------------------------------------------------------- + +-- instance Brain Creature where +-- step = brainVia GormlakBrain +-- entityCanMove = const True + +-- instance Entity Creature where +-- blocksVision _ = False +-- description = view $ Creature.creatureType . Raw.description +-- entityChar = view $ Creature.creatureType . char diff --git a/users/grfn/xanthous/src/Xanthous/App.hs b/users/grfn/xanthous/src/Xanthous/App.hs new file mode 100644 index 000000000000..a251833955cd --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/App.hs @@ -0,0 +1,607 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +{-# OPTIONS_GHC -Wno-deferred-type-errors #-} +module Xanthous.App + ( makeApp + , RunType(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Brick hiding (App, halt, continue, raw) +import qualified Brick +import Graphics.Vty.Attributes (defAttr) +import Graphics.Vty.Input.Events (Event(EvKey)) +import Control.Monad.State (get, gets) +import Control.Monad.State.Class (modify) +import Data.Aeson (object, ToJSON) +import qualified Data.Aeson as A +import qualified Data.Vector as V +import System.Exit +import System.Directory (doesFileExist) +import Data.List.NonEmpty (NonEmpty(..)) +import Data.Vector.Lens (toVectorOf) +-------------------------------------------------------------------------------- +import Xanthous.App.Common +import Xanthous.App.Time +import Xanthous.App.Prompt +import Xanthous.App.Autocommands +import Xanthous.Command +import Xanthous.Data + ( move + , Dimensions'(Dimensions) + , positioned + , position + , Position + , (|*|) + , Tiles(..), Hitpoints, fromScalar + ) +import Xanthous.Data.App (ResourceName, Panel(..), AppEvent(..)) +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Data.Levels (prevLevel, nextLevel) +import qualified Xanthous.Data.Levels as Levels +import Xanthous.Data.Entities (blocksObject) +import Xanthous.Game +import Xanthous.Game.State +import Xanthous.Game.Env +import Xanthous.Game.Draw (drawGame) +import Xanthous.Game.Prompt hiding (Fire) +import qualified Xanthous.Messages as Messages +import Xanthous.Random +import Xanthous.Util (removeVectorIndex, useListOf) +import Xanthous.Util.Inflection (toSentence) +import Xanthous.Physics (throwDistance, bluntThrowDamage) +import Xanthous.Data.EntityMap.Graphics (lineOfSight) +import Xanthous.Data.EntityMap (EntityID) +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +import Xanthous.Entities.Common + ( InventoryPosition, describeInventoryPosition, backpack + , wieldableItem, wieldedItems, wielded, itemsWithPosition + , removeItemFromPosition, asWieldedItem, inRightHand + , wieldedItem, items + ) +import qualified Xanthous.Entities.Character as Character +import Xanthous.Entities.Character hiding (pickUpItem) +import Xanthous.Entities.Item (Item, weight) +import qualified Xanthous.Entities.Item as Item +import Xanthous.Entities.Creature (Creature) +import qualified Xanthous.Entities.Creature as Creature +import Xanthous.Entities.Environment + (Door, open, closed, locked, GroundMessage(..), Staircase(..)) +import Xanthous.Entities.RawTypes + ( edible, eatMessage, hitpointsHealed + , attackMessage + ) +import Xanthous.Generators.Level +import qualified Xanthous.Generators.Level.CaveAutomata as CaveAutomata +import qualified Xanthous.Generators.Level.Dungeon as Dungeon +-------------------------------------------------------------------------------- + +type App = Brick.App GameState AppEvent ResourceName + +data RunType = NewGame | LoadGame FilePath + deriving stock (Eq) + +makeApp :: GameEnv -> RunType -> IO App +makeApp env rt = pure $ Brick.App + { appDraw = drawGame + , appChooseCursor = const headMay + , appHandleEvent = \game event -> runAppM (handleEvent event) env game + , appStartEvent = case rt of + NewGame -> runAppM (startEvent >> get) env + LoadGame save -> pure . (savefile ?~ save) + , appAttrMap = const $ attrMap defAttr [] + } + +runAppM :: AppM a -> GameEnv -> GameState -> EventM ResourceName a +runAppM appm ge = fmap fst . runAppT appm ge + +startEvent :: AppM () +startEvent = do + initLevel + modify updateCharacterVision + use (character . characterName) >>= \case + Nothing -> prompt_ @'StringPrompt ["character", "namePrompt"] Uncancellable + $ \(StringResult s) -> do + character . characterName ?= s + say ["welcome"] =<< use character + Just n -> say ["welcome"] $ object [ "characterName" A..= n ] + +initLevel :: AppM () +initLevel = do + level <- genLevel 0 + entities <>= levelToEntityMap level + characterPosition .= level ^. levelCharacterPosition + +-------------------------------------------------------------------------------- + +handleEvent :: BrickEvent ResourceName AppEvent -> AppM (Next GameState) +handleEvent ev = use promptState >>= \case + NoPrompt -> handleNoPromptEvent ev + WaitingPrompt msg pr -> handlePromptEvent msg pr ev + + +handleNoPromptEvent :: BrickEvent ResourceName AppEvent -> AppM (Next GameState) +handleNoPromptEvent (VtyEvent (EvKey k mods)) + | Just command <- commandFromKey k mods + = do messageHistory %= nextTurn + cancelAutocommand + handleCommand command +handleNoPromptEvent (AppEvent AutoContinue) = do + preuse (autocommand . _ActiveAutocommand . _1) >>= traverse_ autoStep + continue +handleNoPromptEvent _ = continue + +handleCommand :: Command -> AppM (Next GameState) +handleCommand Quit = confirm_ ["quit", "confirm"] (liftIO exitSuccess) >> continue +handleCommand (Move dir) = do + newPos <- uses characterPosition $ move dir + collisionAt newPos >>= \case + Nothing -> do + characterPosition .= newPos + stepGameBy =<< uses (character . speed) (|*| Tiles 1) + describeEntitiesAt newPos + Just Combat -> attackAt newPos + Just Stop -> pure () + continue + +handleCommand PickUp = do + pos <- use characterPosition + uses entities (entitiesAtPositionWithType @Item pos) >>= \case + [] -> say_ ["pickUp", "nothingToPickUp"] + [item] -> pickUpItem item + items' -> + menu_ ["pickUp", "menu"] Cancellable (entityMenu_ items') + $ \(MenuResult item) -> pickUpItem item + continue + where + pickUpItem (itemID, item) = do + character %= Character.pickUpItem item + entities . at itemID .= Nothing + say ["pickUp", "pickUp"] $ object [ "item" A..= item ] + stepGameBy 100 -- TODO + +handleCommand Drop = do + takeItemFromInventory_ ["drop", "menu"] Cancellable id + (say_ ["drop", "nothing"]) + $ \(MenuResult item) -> do + entitiesAtCharacter %= (SomeEntity item <|) + say ["drop", "dropped"] $ object [ "item" A..= item ] + continue + +handleCommand PreviousMessage = do + messageHistory %= previousMessage + continue + +handleCommand Open = do + prompt_ @'DirectionPrompt ["open", "prompt"] Cancellable + $ \(DirectionResult dir) -> do + pos <- move dir <$> use characterPosition + doors <- uses entities $ entitiesAtPositionWithType @Door pos + if | null doors -> say_ ["open", "nothingToOpen"] + | any (view $ _2 . locked) doors -> say_ ["open", "locked"] + | all (view $ _2 . open) doors -> say_ ["open", "alreadyOpen"] + | otherwise -> do + for_ doors $ \(eid, _) -> + entities . ix eid . positioned . _SomeEntity . open .= True + say_ ["open", "success"] + pure () + stepGame -- TODO + continue + +handleCommand Close = do + prompt_ @'DirectionPrompt ["close", "prompt"] Cancellable + $ \(DirectionResult dir) -> do + pos <- move dir <$> use characterPosition + (nonDoors, doors) <- uses entities + $ partitionEithers + . toList + . map ( (matching . aside $ _SomeEntity @Door) + . over _2 (view positioned) + ) + . EntityMap.atPositionWithIDs pos + if | null doors -> say_ ["close", "nothingToClose"] + | all (view $ _2 . closed) doors -> say_ ["close", "alreadyClosed"] + | any (view blocksObject . entityAttributes . snd) nonDoors -> + say ["close", "blocked"] + $ object [ "entityDescriptions" + A..= ( toSentence + . map description + . filter (view blocksObject . entityAttributes) + . map snd + ) nonDoors + , "blockOrBlocks" + A..= ( if length nonDoors == 1 + then "blocks" + else "block" + :: Text) + ] + | otherwise -> do + for_ doors $ \(eid, _) -> + entities . ix eid . positioned . _SomeEntity . closed .= True + for_ nonDoors $ \(eid, _) -> + entities . ix eid . position %= move dir + say_ ["close", "success"] + pure () + stepGame -- TODO + continue + +handleCommand Look = do + prompt_ @'PointOnMap ["look", "prompt"] Cancellable + $ \(PointOnMapResult pos) -> revealedEntitiesAtPosition pos >>= \case + Empty -> say_ ["look", "nothing"] + ents -> describeEntities ents + continue + +handleCommand Wait = stepGame >> continue + +handleCommand Eat = do + uses (character . inventory . backpack) + (V.mapMaybe (\item -> (item,) <$> item ^. Item.itemType . edible)) + >>= \case + Empty -> say_ ["eat", "noFood"] + food -> + let foodMenuItem idx (item, edibleItem) + = ( item ^. Item.itemType . char . char + , MenuOption (description item) (idx, item, edibleItem)) + -- TODO refactor to use entityMenu_ + menuItems = mkMenuItems $ imap foodMenuItem food + in menu_ ["eat", "menuPrompt"] Cancellable menuItems + $ \(MenuResult (idx, item, edibleItem)) -> do + character . inventory . backpack %= removeVectorIndex idx + let msg = fromMaybe (Messages.lookup ["eat", "eat"]) + $ edibleItem ^. eatMessage + character . characterHitpoints' += + edibleItem ^. hitpointsHealed . to fromIntegral + message msg $ object ["item" A..= item] + stepGame -- TODO + continue + +handleCommand Read = do + -- TODO allow reading things in the inventory (combo direction+menu prompt?) + prompt_ @'DirectionPrompt ["read", "prompt"] Cancellable + $ \(DirectionResult dir) -> do + pos <- uses characterPosition $ move dir + uses entities + (fmap snd . entitiesAtPositionWithType @GroundMessage pos) >>= \case + Empty -> say_ ["read", "nothing"] + GroundMessage msg :< Empty -> + say ["read", "result"] $ object ["message" A..= msg] + msgs -> + let readAndContinue Empty = pure () + readAndContinue (msg :< msgs') = + prompt @'Continue + ["read", "result"] + (object ["message" A..= msg]) + Cancellable + . const + $ readAndContinue msgs' + readAndContinue _ = error "this is total" + in readAndContinue msgs + continue + +handleCommand ShowInventory = showPanel InventoryPanel >> continue + +handleCommand DescribeInventory = do + selectItemFromInventory_ ["inventory", "describe", "select"] Cancellable id + (say_ ["inventory", "describe", "nothing"]) + $ \(MenuResult (invPos, item)) -> showPanel . ItemDescriptionPanel + $ Item.fullDescription item + <> "\n\n" <> describeInventoryPosition invPos + continue + + +handleCommand Wield = do + takeItemFromInventory_ ["wield", "menu"] Cancellable asWieldedItem + (say_ ["wield", "nothing"]) + $ \(MenuResult item) -> do + prevItems <- character . inventory . wielded <<.= inRightHand item + character . inventory . backpack + <>= fromList (prevItems ^.. wieldedItems . wieldedItem) + say ["wield", "wielded"] item + continue + +handleCommand Fire = do + selectItemFromInventory_ ["fire", "menu"] Cancellable id + (say_ ["fire", "nothing"]) + $ \(MenuResult (invPos, item)) -> + let wt = weight item + dist = throwDistance wt + dam = bluntThrowDamage wt + in if dist < fromScalar 1 + then say_ ["fire", "zeroRange"] + else firePrompt_ ["fire", "target"] Cancellable dist $ + \(FireResult targetPos) -> do + charPos <- use characterPosition + mTarget <- uses entities $ firstEnemy . lineOfSight charPos targetPos + case mTarget of + Just target -> do + creature' <- damageCreature target dam + unless (Creature.isDead creature') $ + let msgPath = ["fire", "fired"] <> [if dam == 0 + then "noDamage" + else "someDamage"] + in say msgPath $ object [ "item" A..= item + , "creature" A..= creature' + ] + Nothing -> + say ["fire", "fired", "noTarget"] $ object [ "item" A..= item ] + character . inventory %= removeItemFromPosition invPos item + entities . EntityMap.atPosition targetPos %= (SomeEntity item <|) + stepGame -- TODO(grfn): should this be based on distance? + continue + where + firstEnemy + :: [(Position, Vector (EntityID, SomeEntity))] + -> Maybe (EntityID, Creature) + firstEnemy los = + let enemies = los >>= \(_, es) -> toList $ headMay es + in enemies ^? folded . below _SomeEntity + +handleCommand Save = + view (config . disableSaving) >>= \case + True -> say_ ["save", "disabled"] >> continue + False -> do + -- TODO default save locations / config file? + use savefile >>= \case + Just filepath -> + stringPromptWithDefault_ + ["save", "location"] + Cancellable + (pack filepath) + promptCallback + Nothing -> prompt_ @'StringPrompt ["save", "location"] Cancellable promptCallback + continue + where + promptCallback :: PromptResult 'StringPrompt -> AppM () + promptCallback (StringResult filename) = do + sf <- use savefile + exists <- liftIO . doesFileExist $ unpack filename + if exists && sf /= Just (unpack filename) + then confirm ["save", "overwrite"] (object ["filename" A..= filename]) + $ doSave filename + else doSave filename + doSave filename = do + src <- gets saveGame + lift . liftIO $ do + writeFile (unpack filename) $ toStrict src + exitSuccess + +handleCommand GoUp = do + hasStairs <- uses entitiesAtCharacter $ elem (SomeEntity UpStaircase) + if hasStairs + then uses levels prevLevel >>= \case + Just levs' -> do + cEID <- use characterEntityID + pCharacter <- entities . at cEID <<.= Nothing + levels .= levs' + charPos <- use characterPosition + entities . at cEID .= pCharacter + characterPosition .= charPos + Nothing -> + -- TODO in nethack, this leaves the game. Maybe something similar here? + say_ ["cant", "goUp"] + else say_ ["cant", "goUp"] + + continue + +handleCommand GoDown = do + hasStairs <- uses entitiesAtCharacter $ elem (SomeEntity DownStaircase) + + if hasStairs + then do + levs <- use levels + let newLevelNum = Levels.pos levs + 1 + levs' <- nextLevel (levelToGameLevel <$> genLevel newLevelNum) levs + cEID <- use characterEntityID + pCharacter <- entities . at cEID <<.= Nothing + levels .= levs' + entities . at cEID .= pCharacter + characterPosition .= extract levs' ^. upStaircasePosition + else say_ ["cant", "goDown"] + + continue + +handleCommand (StartAutoMove dir) = do + runAutocommand $ AutoMove dir + continue + +handleCommand Rest = do + say_ ["autocommands", "resting"] + runAutocommand AutoRest + continue + +-- + +handleCommand ToggleRevealAll = do + val <- debugState . allRevealed <%= not + say ["debug", "toggleRevealAll"] $ object [ "revealAll" A..= val ] + continue + +-------------------------------------------------------------------------------- +attackAt :: Position -> AppM () +attackAt pos = + uses entities (entitiesAtPositionWithType @Creature pos) >>= \case + Empty -> say_ ["combat", "nothingToAttack"] + (creature :< Empty) -> attackCreature creature + creatures -> + menu_ ["combat", "menu"] Cancellable (entityMenu_ creatures) + $ \(MenuResult creature) -> attackCreature creature + where + attackCreature creature = do + charDamage <- uses character characterDamage + creature' <- damageCreature creature charDamage + msg <- uses character getAttackMessage + unless (Creature.isDead creature') + . message msg $ object ["creature" A..= creature'] + whenM (uses character $ isNothing . weapon) handleFists + stepGame + weapon chr = chr ^? inventory . wielded . wieldedItems . wieldableItem + getAttackMessage chr = + case weapon chr of + Just wi -> + fromMaybe (Messages.lookup ["combat", "hit", "generic"]) + $ wi ^. attackMessage + Nothing -> + Messages.lookup ["combat", "hit", "fists"] + + handleFists = do + damageChance <- use $ character . body . knuckles . to fistDamageChance + whenM (chance damageChance) $ do + damageAmount <- use $ character . body . knuckles . to fistfightingDamage + say_ [ "combat" , if damageAmount > 1 + then "fistExtraSelfDamage" + else "fistSelfDamage" ] + character %= Character.damage damageAmount + character . body . knuckles %= damageKnuckles + +damageCreature :: (EntityID, Creature) -> Hitpoints -> AppM Creature +damageCreature (creatureID, creature) dam = do + let creature' = Creature.damage dam creature + msgParams = object ["creature" A..= creature'] + if Creature.isDead creature' + then do + say ["combat", "killed"] msgParams + floorItems <- useListOf + $ entities + . ix creatureID + . positioned + . _SomeEntity @Creature + . inventory + . items + mCreaturePos <- preuse $ entities . ix creatureID . position + entities . at creatureID .= Nothing + for_ mCreaturePos $ \creaturePos -> + entities . EntityMap.atPosition creaturePos + %= (<> fromList (SomeEntity <$> floorItems)) + else entities . ix creatureID . positioned .= SomeEntity creature' + pure creature' + + +entityMenu_ + :: (Comonad w, Entity entity) + => [w entity] + -> Map Char (MenuOption (w entity)) +entityMenu_ = mkMenuItems @[_] . map entityMenuItem + where + entityMenuItem wentity + = let entity = extract wentity + in (entityMenuChar entity, MenuOption (description entity) wentity) + + +entityMenuChar :: Entity a => a -> Char +entityMenuChar entity + = let ec = entityChar entity ^. char + in if ec `elem` (['a'..'z'] ++ ['A'..'Z']) + then ec + else 'a' + +-- | Prompt with an item to select out of the inventory and call callback with +-- it +selectItemFromInventory + :: forall item params. + (ToJSON params) + => [Text] -- ^ Menu message + -> params -- ^ Menu message params + -> PromptCancellable -- ^ Is the menu cancellable? + -> Prism' Item item -- ^ Attach some extra information to the item, in a + -- recoverable fashion. Prism vs iso so we can discard + -- items. + -> AppM () -- ^ Action to take if there are no items matching + -> (PromptResult ('Menu (InventoryPosition, item)) -> AppM ()) + -> AppM () +selectItemFromInventory msgPath msgParams cancellable extraInfo onEmpty cb = do + uses (character . inventory) + (V.mapMaybe (_2 $ preview extraInfo) . toVectorOf itemsWithPosition) + >>= \case + Empty -> onEmpty + items' -> menu msgPath msgParams cancellable (itemMenu items') cb + where + itemMenu = mkMenuItems . map itemMenuItem + itemMenuItem (invPos, extraInfoItem) = + let item = extraInfo # extraInfoItem + in ( entityMenuChar item + , MenuOption + (description item <> " (" <> describeInventoryPosition invPos <> ")") + (invPos, extraInfoItem) + ) + +-- | Prompt with an item to select out of the inventory and call callback with +-- it +selectItemFromInventory_ + :: forall item. + [Text] -- ^ Menu message + -> PromptCancellable -- ^ Is the menu cancellable? + -> Prism' Item item -- ^ Attach some extra information to the item, in a + -- recoverable fashion. Prism vs iso so we can discard + -- items. + -> AppM () -- ^ Action to take if there are no items matching + -> (PromptResult ('Menu (InventoryPosition, item)) -> AppM ()) + -> AppM () +selectItemFromInventory_ msgPath = selectItemFromInventory msgPath () + +-- | Prompt with an item to select out of the inventory, remove it from the +-- inventory, and call callback with it +takeItemFromInventory + :: forall item params. + (ToJSON params) + => [Text] -- ^ Menu message + -> params -- ^ Menu message params + -> PromptCancellable -- ^ Is the menu cancellable? + -> Prism' Item item -- ^ Attach some extra information to the item, in a + -- recoverable fashion. Prism vs iso so we can discard + -- items. + -> AppM () -- ^ Action to take if there are no items matching + -> (PromptResult ('Menu item) -> AppM ()) + -> AppM () +takeItemFromInventory msgPath msgParams cancellable extraInfo onEmpty cb = + selectItemFromInventory msgPath msgParams cancellable extraInfo onEmpty + $ \(MenuResult (invPos, item)) -> do + character . inventory + %= removeItemFromPosition invPos (item ^. re extraInfo) + cb $ MenuResult item + +takeItemFromInventory_ + :: forall item. + [Text] -- ^ Menu message + -> PromptCancellable -- ^ Is the menu cancellable? + -> Prism' Item item -- ^ Attach some extra information to the item, in a + -- recoverable fashion. Prism vs iso so we can discard + -- items. + -> AppM () -- ^ Action to take if there are no items matching + -> (PromptResult ('Menu item) -> AppM ()) + -> AppM () +takeItemFromInventory_ msgPath = takeItemFromInventory msgPath () + +-- entityMenu :: Entity entity => [entity] -> Map Char (MenuOption entity) +-- entityMenu = map (map runIdentity) . entityMenu_ . fmap Identity + +showPanel :: Panel -> AppM () +showPanel panel = do + activePanel ?= panel + prompt_ @'Continue ["generic", "continue"] Uncancellable + . const + $ activePanel .= Nothing + +-------------------------------------------------------------------------------- + +genLevel + :: Word -- ^ Level number, starting at 0 + -> AppM Level +genLevel num = do + let dims = Dimensions 80 80 + generator <- choose $ CaveAutomata :| [Dungeon] + let + doGen = case generator of + CaveAutomata -> generateLevel SCaveAutomata CaveAutomata.defaultParams + Dungeon -> generateLevel SDungeon Dungeon.defaultParams + level <- doGen dims num + pure $!! level + +levelToGameLevel :: Level -> GameLevel +levelToGameLevel level = + let _levelEntities = levelToEntityMap level + _upStaircasePosition = level ^. levelCharacterPosition + _levelRevealedPositions = mempty + in GameLevel {..} diff --git a/users/grfn/xanthous/src/Xanthous/App/Autocommands.hs b/users/grfn/xanthous/src/Xanthous/App/Autocommands.hs new file mode 100644 index 000000000000..5d4db1a47465 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/App/Autocommands.hs @@ -0,0 +1,76 @@ +-------------------------------------------------------------------------------- +module Xanthous.App.Autocommands + ( runAutocommand + , autoStep + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Control.Concurrent (threadDelay) +import qualified Data.Aeson as A +import Data.Aeson (object) +import Data.List.NonEmpty (nonEmpty) +import qualified Data.List.NonEmpty as NE +import Control.Monad.State (gets) +-------------------------------------------------------------------------------- +import Xanthous.App.Common +import Xanthous.App.Time +import Xanthous.Data +import Xanthous.Data.App +import Xanthous.Entities.Character (speed, isFullyHealed) +import Xanthous.Entities.Creature (Creature, creatureType) +import Xanthous.Entities.RawTypes (hostile) +import Xanthous.Game.State +-------------------------------------------------------------------------------- + +-- | Step the given autocommand forward once +autoStep :: Autocommand -> AppM () +autoStep (AutoMove dir) = do + newPos <- uses characterPosition $ move dir + collisionAt newPos >>= \case + Nothing -> do + characterPosition .= newPos + stepGameBy =<< uses (character . speed) (|*| (1 :: Tiles)) + describeEntitiesAt newPos + cancelIfDanger + Just _ -> cancelAutocommand + +autoStep AutoRest = do + done <- uses character isFullyHealed + if done + then say_ ["autocommands", "doneResting"] >> cancelAutocommand + else stepGame >> cancelIfDanger + +-- | Cancel the autocommand if the character is in danger +cancelIfDanger :: AppM () +cancelIfDanger = do + maybeVisibleEnemies <- nonEmpty <$> enemiesInSight + for_ maybeVisibleEnemies $ \visibleEnemies -> do + say ["autocommands", "enemyInSight"] + $ object [ "firstEntity" A..= NE.head visibleEnemies ] + cancelAutocommand + where + enemiesInSight :: AppM [Creature] + enemiesInSight = do + ents <- gets characterVisibleEntities + pure $ ents + ^.. folded + . _SomeEntity @Creature + . filtered (view $ creatureType . hostile) + +-------------------------------------------------------------------------------- + +autocommandIntervalฮผs :: Int +autocommandIntervalฮผs = 1000 * 50 -- 50 ms + +runAutocommand :: Autocommand -> AppM () +runAutocommand ac = do + env <- ask + tid <- liftIO . async $ runReaderT go env + autocommand .= ActiveAutocommand ac tid + where + go = everyฮผs autocommandIntervalฮผs $ sendEvent AutoContinue + +-- | Perform 'act' every ฮผs microseconds forever +everyฮผs :: MonadIO m => Int -> m () -> m () +everyฮผs ฮผs act = act >> liftIO (threadDelay ฮผs) >> everyฮผs ฮผs act diff --git a/users/grfn/xanthous/src/Xanthous/App/Common.hs b/users/grfn/xanthous/src/Xanthous/App/Common.hs new file mode 100644 index 000000000000..69ba6f0e0596 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/App/Common.hs @@ -0,0 +1,67 @@ +-------------------------------------------------------------------------------- +module Xanthous.App.Common + ( describeEntities + , describeEntitiesAt + , entitiesAtPositionWithType + + -- * Re-exports + , MonadState + , MonadRandom + , EntityMap + , module Xanthous.Game.Lenses + , module Xanthous.Monad + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson (object) +import qualified Data.Aeson as A +import Control.Monad.State (MonadState) +import Control.Monad.Random (MonadRandom) +-------------------------------------------------------------------------------- +import Xanthous.Data (Position, positioned) +import Xanthous.Data.EntityMap (EntityMap) +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Game +import Xanthous.Game.Lenses +import Xanthous.Game.State +import Xanthous.Monad +import Xanthous.Entities.Character (Character) +import Xanthous.Util.Inflection (toSentence) +-------------------------------------------------------------------------------- + +entitiesAtPositionWithType + :: forall a. (Entity a, Typeable a) + => Position + -> EntityMap SomeEntity + -> [(EntityMap.EntityID, a)] +entitiesAtPositionWithType pos em = + let someEnts = EntityMap.atPositionWithIDs pos em + in flip foldMap someEnts $ \(eid, view positioned -> se) -> + case downcastEntity @a se of + Just e -> [(eid, e)] + Nothing -> [] + +describeEntitiesAt :: (MonadState GameState m, MonadRandom m) => Position -> m () +describeEntitiesAt pos = + use ( entities + . EntityMap.atPosition pos + . to (filter (not . entityIs @Character)) + ) >>= \case + Empty -> pure () + ents -> describeEntities ents + +describeEntities + :: ( Entity entity + , MonadRandom m + , MonadState GameState m + , MonoFoldable (f Text) + , Functor f + , Element (f Text) ~ Text + ) + => f entity + -> m () +describeEntities ents = + let descriptions = description <$> ents + in say ["entities", "description"] + $ object ["entityDescriptions" A..= toSentence descriptions] diff --git a/users/grfn/xanthous/src/Xanthous/App/Prompt.hs b/users/grfn/xanthous/src/Xanthous/App/Prompt.hs new file mode 100644 index 000000000000..799281a1c2fd --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/App/Prompt.hs @@ -0,0 +1,228 @@ +{-# LANGUAGE UndecidableInstances #-} +-------------------------------------------------------------------------------- +module Xanthous.App.Prompt + ( handlePromptEvent + , clearPrompt + , prompt + , prompt_ + , stringPromptWithDefault + , stringPromptWithDefault_ + , confirm_ + , confirm + , menu + , menu_ + , firePrompt_ + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick (BrickEvent(..), Next) +import Brick.Widgets.Edit (handleEditorEvent) +import Data.Aeson (ToJSON, object) +import Graphics.Vty.Input.Events (Event(EvKey), Key(..)) +-------------------------------------------------------------------------------- +import Xanthous.App.Common +import Xanthous.Data (move, Tiles, Position, positioned, _Position) +import qualified Xanthous.Data as Data +import Xanthous.Command (directionFromChar) +import Xanthous.Data.App (ResourceName, AppEvent) +import Xanthous.Game.Prompt +import Xanthous.Game.State +import qualified Xanthous.Messages as Messages +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Entities.Creature (creatureType, Creature) +import Xanthous.Entities.RawTypes (hostile) +import qualified Linear.Metric as Metric +-------------------------------------------------------------------------------- + +handlePromptEvent + :: Text -- ^ Prompt message + -> Prompt AppM + -> BrickEvent ResourceName AppEvent + -> AppM (Next GameState) + +handlePromptEvent _ (Prompt Cancellable _ _ _ _) (VtyEvent (EvKey KEsc [])) + = clearPrompt >> continue +handlePromptEvent _ pr (VtyEvent (EvKey KEnter [])) + = clearPrompt >> submitPrompt pr >> continue + +handlePromptEvent _ pr@(Prompt _ SConfirm _ _ _) (VtyEvent (EvKey (KChar 'y') [])) + = clearPrompt >> submitPrompt pr >> continue + +handlePromptEvent _ (Prompt _ SConfirm _ _ _) (VtyEvent (EvKey (KChar 'n') [])) + = clearPrompt >> continue + +handlePromptEvent + msg + (Prompt c SStringPrompt (StringPromptState edit) pri cb) + (VtyEvent ev) + = do + edit' <- lift $ handleEditorEvent ev edit + let prompt' = Prompt c SStringPrompt (StringPromptState edit') pri cb + promptState .= WaitingPrompt msg prompt' + continue + +handlePromptEvent _ (Prompt _ SDirectionPrompt _ _ cb) + (VtyEvent (EvKey (KChar (directionFromChar -> Just dir)) [])) + = clearPrompt >> cb (DirectionResult dir) >> continue +handlePromptEvent _ (Prompt _ SDirectionPrompt _ _ _) _ = continue + +handlePromptEvent _ (Prompt _ SMenu _ items' cb) (VtyEvent (EvKey (KChar chr) [])) + | Just (MenuOption _ res) <- items' ^. at chr + = clearPrompt >> cb (MenuResult res) >> continue + | otherwise + = continue + +handlePromptEvent + msg + (Prompt c SPointOnMap (PointOnMapPromptState pos) pri cb) + (VtyEvent (EvKey (KChar (directionFromChar -> Just dir)) [])) + = let pos' = move dir pos + prompt' = Prompt c SPointOnMap (PointOnMapPromptState pos') pri cb + in promptState .= WaitingPrompt msg prompt' + >> continue +handlePromptEvent _ (Prompt _ SPointOnMap _ _ _) _ = continue + +handlePromptEvent + msg + (Prompt c SFire (FirePromptState pos) pri@(origin, range) cb) + (VtyEvent (EvKey (KChar (directionFromChar -> Just dir)) [])) + = do + let pos' = move dir pos + prompt' = Prompt c SFire (FirePromptState pos') pri cb + when (Data.distance origin pos' <= range) $ + promptState .= WaitingPrompt msg prompt' + continue + +handlePromptEvent + _ + (Prompt Cancellable _ _ _ _) + (VtyEvent (EvKey (KChar 'q') [])) + = clearPrompt >> continue +handlePromptEvent _ _ _ = continue + +clearPrompt :: AppM () +clearPrompt = promptState .= NoPrompt + +type PromptParams :: PromptType -> Type +type family PromptParams pt where + PromptParams ('Menu a) = Map Char (MenuOption a) -- Menu items + PromptParams 'Fire = Tiles -- Range + PromptParams _ = () + +prompt + :: forall (pt :: PromptType) (params :: Type). + (ToJSON params, SingPromptType pt, PromptParams pt ~ ()) + => [Text] -- ^ Message key + -> params -- ^ Message params + -> PromptCancellable + -> (PromptResult pt -> AppM ()) -- ^ Prompt promise handler + -> AppM () +prompt msgPath params cancellable cb = do + let pt = singPromptType @pt + msg <- Messages.message msgPath params + mp :: Maybe (Prompt AppM) <- case pt of + SPointOnMap -> do + charPos <- use characterPosition + pure . Just $ mkPointOnMapPrompt cancellable charPos cb + SStringPrompt -> pure . Just $ mkStringPrompt cancellable cb + SConfirm -> pure . Just $ mkPrompt cancellable pt cb + SDirectionPrompt -> pure . Just $ mkPrompt cancellable pt cb + SContinue -> pure . Just $ mkPrompt cancellable pt cb + for_ mp $ \p -> promptState .= WaitingPrompt msg p + +prompt_ + :: forall (pt :: PromptType). + (SingPromptType pt, PromptParams pt ~ ()) + => [Text] -- ^ Message key + -> PromptCancellable + -> (PromptResult pt -> AppM ()) -- ^ Prompt promise handler + -> AppM () +prompt_ msg = prompt msg $ object [] + +stringPromptWithDefault + :: forall (params :: Type). (ToJSON params) + => [Text] -- ^ Message key + -> params -- ^ Message params + -> PromptCancellable + -> Text -- ^ Prompt default + -> (PromptResult 'StringPrompt -> AppM ()) -- ^ Prompt promise handler + -> AppM () +stringPromptWithDefault msgPath params cancellable def cb = do + msg <- Messages.message msgPath params + let p = mkStringPromptWithDefault cancellable def cb + promptState .= WaitingPrompt msg p + +stringPromptWithDefault_ + :: [Text] -- ^ Message key + -> PromptCancellable + -> Text -- ^ Prompt default + -> (PromptResult 'StringPrompt -> AppM ()) -- ^ Prompt promise handler + -> AppM () +stringPromptWithDefault_ msg = stringPromptWithDefault msg $ object [] + +confirm + :: ToJSON params + => [Text] -- ^ Message key + -> params + -> AppM () + -> AppM () +confirm msgPath params + = prompt @'Confirm msgPath params Cancellable . const + +confirm_ :: [Text] -> AppM () -> AppM () +confirm_ msgPath = confirm msgPath $ object [] + +menu :: forall (a :: Type) (params :: Type). + (ToJSON params) + => [Text] -- ^ Message key + -> params -- ^ Message params + -> PromptCancellable + -> Map Char (MenuOption a) -- ^ Menu items + -> (PromptResult ('Menu a) -> AppM ()) -- ^ Menu promise handler + -> AppM () +menu msgPath params cancellable items' cb = do + msg <- Messages.message msgPath params + let p = mkMenu cancellable items' cb + promptState .= WaitingPrompt msg p + +menu_ :: forall (a :: Type). + [Text] -- ^ Message key + -> PromptCancellable + -> Map Char (MenuOption a) -- ^ Menu items + -> (PromptResult ('Menu a) -> AppM ()) -- ^ Menu promise handler + -> AppM () +menu_ msgPath = menu msgPath $ object [] + +firePrompt_ + :: [Text] -- ^ Message key + -> PromptCancellable + -> Tiles -- ^ Range + -> (PromptResult 'Fire -> AppM ()) -- ^ Promise handler + -> AppM () +firePrompt_ msgPath cancellable range cb = do + msg <- Messages.message msgPath $ object [] + initialPos <- maybe (use characterPosition) pure =<< nearestEnemyPosition + let p = mkFirePrompt cancellable initialPos range cb + promptState .= WaitingPrompt msg p + +-- | Returns the position of the nearest visible hostile creature, if any +nearestEnemyPosition :: AppM (Maybe Position) +nearestEnemyPosition = do + charPos <- use characterPosition + em <- use entities + ps <- characterVisiblePositions + let candidates = toList ps >>= \p -> + let ents = EntityMap.atPositionWithIDs p em + in ents + ^.. folded + . _2 + . positioned + . _SomeEntity @Creature + . creatureType + . filtered (view hostile) + . to (const (distance charPos p, p)) + pure . headMay . fmap snd $ sortOn fst candidates + where + distance :: Position -> Position -> Double + distance = Metric.distance `on` (fmap fromIntegral . view _Position) diff --git a/users/grfn/xanthous/src/Xanthous/App/Time.hs b/users/grfn/xanthous/src/Xanthous/App/Time.hs new file mode 100644 index 000000000000..cca352858d9c --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/App/Time.hs @@ -0,0 +1,42 @@ +-------------------------------------------------------------------------------- +module Xanthous.App.Time + ( stepGame + , stepGameBy + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import System.Exit +-------------------------------------------------------------------------------- +import Xanthous.Data (Ticks) +import Xanthous.App.Prompt +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Entities.Character (isDead) +import Xanthous.Game.State +import Xanthous.Game.Prompt +import Xanthous.Game.Lenses +import Control.Monad.State (modify) +import qualified Xanthous.Game.Memo as Memo +-------------------------------------------------------------------------------- + + +stepGameBy :: Ticks -> AppM () +stepGameBy ticks = do + ents <- uses entities EntityMap.toEIDsAndPositioned + for_ ents $ \(eid, pEntity) -> do + pEntity' <- step ticks pEntity + entities . ix eid .= pEntity' + + clearMemo Memo.characterVisiblePositions + modify updateCharacterVision + + whenM (uses character isDead) + . prompt_ @'Continue ["dead"] Uncancellable + . const . lift . liftIO + $ exitSuccess + +ticksPerTurn :: Ticks +ticksPerTurn = 100 + +stepGame :: AppM () +stepGame = stepGameBy ticksPerTurn diff --git a/users/grfn/xanthous/src/Xanthous/Command.hs b/users/grfn/xanthous/src/Xanthous/Command.hs new file mode 100644 index 000000000000..187e5c16d7da --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Command.hs @@ -0,0 +1,84 @@ +-------------------------------------------------------------------------------- +module Xanthous.Command where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (Left, Right, Down) +-------------------------------------------------------------------------------- +import Graphics.Vty.Input (Key(..), Modifier(..)) +import qualified Data.Char as Char +-------------------------------------------------------------------------------- +import Xanthous.Data (Direction(..)) +-------------------------------------------------------------------------------- + +data Command + = Quit + | Move Direction + | StartAutoMove Direction + | PreviousMessage + | PickUp + | Drop + | Open + | Close + | Wait + | Eat + | Look + | Save + | Read + | ShowInventory + | DescribeInventory + | Wield + | Fire + | GoUp + | GoDown + | Rest + + -- | TODO replace with `:` commands + | ToggleRevealAll + +commandFromKey :: Key -> [Modifier] -> Maybe Command +commandFromKey (KChar 'q') [] = Just Quit +commandFromKey (KChar '.') [] = Just Wait +commandFromKey (KChar (directionFromChar -> Just dir)) [] = Just $ Move dir +commandFromKey (KChar c) [] + | Char.isUpper c + , Just dir <- directionFromChar $ Char.toLower c + = Just $ StartAutoMove dir +commandFromKey (KChar 'p') [MCtrl] = Just PreviousMessage +commandFromKey (KChar ',') [] = Just PickUp +commandFromKey (KChar 'd') [] = Just Drop +commandFromKey (KChar 'o') [] = Just Open +commandFromKey (KChar 'c') [] = Just Close +commandFromKey (KChar ';') [] = Just Look +commandFromKey (KChar 'e') [] = Just Eat +commandFromKey (KChar 'S') [] = Just Save +commandFromKey (KChar 'r') [] = Just Read +commandFromKey (KChar 'i') [] = Just ShowInventory +commandFromKey (KChar 'I') [] = Just DescribeInventory +commandFromKey (KChar 'w') [] = Just Wield +commandFromKey (KChar 'f') [] = Just Fire +commandFromKey (KChar '<') [] = Just GoUp +commandFromKey (KChar '>') [] = Just GoDown +commandFromKey (KChar 'R') [] = Just Rest + +commandFromKey KUp [] = Just $ Move Up +commandFromKey KDown [] = Just $ Move Down +commandFromKey KLeft [] = Just $ Move Left +commandFromKey KRight [] = Just $ Move Right + +-- DEBUG COMMANDS -- +commandFromKey (KChar 'r') [MMeta] = Just ToggleRevealAll + +commandFromKey _ _ = Nothing + +-------------------------------------------------------------------------------- + +directionFromChar :: Char -> Maybe Direction +directionFromChar 'h' = Just Left +directionFromChar 'j' = Just Down +directionFromChar 'k' = Just Up +directionFromChar 'l' = Just Right +directionFromChar 'y' = Just UpLeft +directionFromChar 'u' = Just UpRight +directionFromChar 'b' = Just DownLeft +directionFromChar 'n' = Just DownRight +directionFromChar '.' = Just Here +directionFromChar _ = Nothing diff --git a/users/grfn/xanthous/src/Xanthous/Data.hs b/users/grfn/xanthous/src/Xanthous/Data.hs new file mode 100644 index 000000000000..1b67e0f160db --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data.hs @@ -0,0 +1,818 @@ +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RoleAnnotations #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE NoTypeSynonymInstances #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +-------------------------------------------------------------------------------- +-- | Common data types for Xanthous ------------------------------------------------------------------------------ +module Xanthous.Data + ( Opposite(..) + + -- * + , Position'(..) + , Position + , x + , y + + -- ** + , Positioned(..) + , _Positioned + , position + , positioned + , loc + , _Position + , positionFromPair + , positionFromV2 + , addPositions + , diffPositions + , stepTowards + , isUnit + , distance + + -- * Boxes + , Box(..) + , topLeftCorner + , bottomRightCorner + , setBottomRightCorner + , dimensions + , inBox + , boxIntersects + , boxCenter + , boxEdge + , module Linear.V2 + + -- * Unit math + , Scalar(..) + , Per(..) + , invertRate + , invertedRate + , (|+|) + , (|*|) + , (|/|) + , (:+:) + , (:*:) + , (:/:) + , (:**:)(..) + , Ticks(..) + , Tiles(..) + , TicksPerTile + , TilesPerTick + , timesTiles + , Square(..) + , squared + , Cubic(..) + , Grams + , Meters + , Uno(..) + , Unit(..) + , UnitSymbol(..) + + -- * + , Dimensions'(..) + , Dimensions + , HasWidth(..) + , HasHeight(..) + + -- * + , Direction(..) + , move + , asPosition + , directionOf + , Cardinal(..) + + -- * + , Corner(..) + , Edge(..) + , cornerEdges + + -- * + , Neighbors(..) + , edges + , neighborDirections + , neighborPositions + , neighborCells + , arrayNeighbors + , rotations + , HasTopLeft(..) + , HasTop(..) + , HasTopRight(..) + , HasLeft(..) + , HasRight(..) + , HasBottomLeft(..) + , HasBottom(..) + , HasBottomRight(..) + + -- * + , Hitpoints(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (Left, Down, Right, (.=), elements) +-------------------------------------------------------------------------------- +import Linear.V2 hiding (_x, _y) +import qualified Linear.V2 as L +import Linear.V4 hiding (_x, _y) +import Test.QuickCheck (CoArbitrary, Function, elements) +import Test.QuickCheck.Arbitrary.Generic +import Data.Group +import Brick (Location(Location), Edges(..)) +import Data.Monoid (Product(..), Sum(..)) +import Data.Array.IArray +import Data.Aeson.Generic.DerivingVia +import Data.Aeson + ( ToJSON(..), FromJSON(..), object, (.=), (.:), withObject) +import Data.Random (Distribution) +import Data.Coerce +import Data.Proxy (Proxy(Proxy)) +-------------------------------------------------------------------------------- +import Xanthous.Util (EqEqProp(..), EqProp, between) +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +import Xanthous.Orphans () +import Xanthous.Util.Graphics +import qualified Linear.Metric as Metric +-------------------------------------------------------------------------------- + +-- | opposite โ opposite โก id +class Opposite x where + opposite :: x -> x + +-------------------------------------------------------------------------------- + +-- fromScalar โ scalar โก id +class Scalar a where + scalar :: a -> Double + fromScalar :: Double -> a + +instance Scalar Double where + scalar = id + fromScalar = id + +newtype ScalarIntegral a = ScalarIntegral a + deriving newtype (Eq, Ord, Num, Enum, Real, Integral) +instance Integral a => Scalar (ScalarIntegral a) where + scalar = fromIntegral + fromScalar = floor + +deriving via (ScalarIntegral Integer) instance Scalar Integer +deriving via (ScalarIntegral Word) instance Scalar Word + +-- | Units of measure +class Unit a where + unitSuffix :: Text +type UnitSymbol :: Symbol -> Type -> Type +newtype UnitSymbol suffix a = UnitSymbol a +instance KnownSymbol suffix => Unit (UnitSymbol suffix a) where + unitSuffix = pack $ symbolVal @suffix Proxy + +newtype ShowUnitSuffix a b = ShowUnitSuffix a +instance (Show b, Unit a, Coercible a b) => Show (ShowUnitSuffix a b) where + show a = show (coerce @_ @b a) <> " " <> unpack (unitSuffix @a) + +-------------------------------------------------------------------------------- + +data Position' a where + Position :: { _x :: a + , _y :: a + } -> (Position' a) + deriving stock (Show, Eq, Generic, Ord, Functor, Foldable, Traversable) + deriving anyclass (NFData, Hashable, CoArbitrary, Function) + deriving EqProp via EqEqProp (Position' a) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + (Position' a) + +x, y :: Lens' (Position' a) a +x = lens (\(Position xx _) -> xx) (\(Position _ yy) xx -> Position xx yy) +y = lens (\(Position _ yy) -> yy) (\(Position xx _) yy -> Position xx yy) + +type Position = Position' Int + +instance Arbitrary a => Arbitrary (Position' a) where + arbitrary = genericArbitrary + shrink (Position px py) = Position <$> shrink px <*> shrink py + + +instance Num a => Semigroup (Position' a) where + (Position xโ yโ) <> (Position xโ yโ) = Position (xโ + xโ) (yโ + yโ) + +instance Num a => Monoid (Position' a) where + mempty = Position 0 0 + +instance Num a => Group (Position' a) where + invert (Position px py) = Position (negate px) (negate py) + +-- | Positions convert to scalars by discarding their orientation and just +-- measuring the length from the origin +instance (Ord a, Num a, Scalar a) => Scalar (Position' a) where + scalar = fromIntegral . length . line 0 . view _Position + fromScalar n = Position (fromScalar n) (fromScalar n) + +data Positioned a where + Positioned :: Position -> a -> Positioned a + deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic) + deriving anyclass (NFData, CoArbitrary, Function) +type role Positioned representational + +_Positioned :: Iso (Position, a) (Position, b) (Positioned a) (Positioned b) +_Positioned = iso hither yon + where + hither (pos, a) = Positioned pos a + yon (Positioned pos b) = (pos, b) + +instance Arbitrary a => Arbitrary (Positioned a) where + arbitrary = Positioned <$> arbitrary <*> arbitrary + +instance ToJSON a => ToJSON (Positioned a) where + toJSON (Positioned pos val) = object + [ "position" .= pos + , "data" .= val + ] + +instance FromJSON a => FromJSON (Positioned a) where + parseJSON = withObject "Positioned" $ \obj -> + Positioned <$> obj .: "position" <*> obj .: "data" + +position :: Lens' (Positioned a) Position +position = lens + (\(Positioned pos _) -> pos) + (\(Positioned _ a) pos -> Positioned pos a) + +positioned :: Lens (Positioned a) (Positioned b) a b +positioned = lens + (\(Positioned _ x') -> x') + (\(Positioned pos _) x' -> Positioned pos x') + +loc :: Iso' Position Location +loc = iso hither yon + where + hither (Position px py) = Location (px, py) + yon (Location (lx, ly)) = Position lx ly + +_Position :: Iso' (Position' a) (V2 a) +_Position = iso hither yon + where + hither (Position px py) = V2 px py + yon (V2 lx ly) = Position lx ly + +positionFromPair :: (Num a, Integral i, Integral j) => (i, j) -> Position' a +positionFromPair (i, j) = Position (fromIntegral i) (fromIntegral j) + +positionFromV2 :: (Num a, Integral i) => V2 i -> Position' a +positionFromV2 (V2 xx yy) = Position (fromIntegral xx) (fromIntegral yy) + +-- | Add two positions +-- +-- Operation for the additive group on positions +addPositions :: Num a => Position' a -> Position' a -> Position' a +addPositions = (<>) + +-- | Subtract two positions. +-- +-- diffPositions posโ posโ = posโ `addPositions` (invert posโ) +diffPositions :: Num a => Position' a -> Position' a -> Position' a +diffPositions (Position xโ yโ) (Position xโ yโ) = Position (xโ - xโ) (yโ - yโ) + +-- | Is this position a unit position? or: When taken as a difference, does this +-- position represent a step of one tile? +-- +-- โ dir :: Direction. isUnit ('asPosition' dir) +isUnit :: (Eq a, Num a) => Position' a -> Bool +isUnit (Position px py) = + abs px `elem` [0,1] && abs py `elem` [0, 1] && (px, py) /= (0, 0) + +-------------------------------------------------------------------------------- + +data Dimensions' a = Dimensions + { _width :: a + , _height :: a + } + deriving stock (Show, Eq, Functor, Generic) + deriving anyclass (CoArbitrary, Function) +makeFieldsNoPrefix ''Dimensions' + +instance Arbitrary a => Arbitrary (Dimensions' a) where + arbitrary = Dimensions <$> arbitrary <*> arbitrary + +type Dimensions = Dimensions' Word + +-------------------------------------------------------------------------------- + +data Direction where + Up :: Direction + Down :: Direction + Left :: Direction + Right :: Direction + UpLeft :: Direction + UpRight :: Direction + DownLeft :: Direction + DownRight :: Direction + Here :: Direction + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (CoArbitrary, Function, NFData, ToJSON, FromJSON, Hashable) + deriving Arbitrary via GenericArbitrary Direction + +instance Opposite Direction where + opposite Up = Down + opposite Down = Up + opposite Left = Right + opposite Right = Left + opposite UpLeft = DownRight + opposite UpRight = DownLeft + opposite DownLeft = UpRight + opposite DownRight = UpLeft + opposite Here = Here + +move :: Num a => Direction -> Position' a -> Position' a +move Up = y -~ 1 +move Down = y +~ 1 +move Left = x -~ 1 +move Right = x +~ 1 +move UpLeft = move Up . move Left +move UpRight = move Up . move Right +move DownLeft = move Down . move Left +move DownRight = move Down . move Right +move Here = id + +asPosition :: Direction -> Position +asPosition dir = move dir mempty + +-- | Returns the direction that a given position is from a given source position +directionOf + :: Position -- ^ Source + -> Position -- ^ Target + -> Direction +directionOf (Position xโ yโ) (Position xโ yโ) = + case (xโ `compare` xโ, yโ `compare` yโ) of + (EQ, EQ) -> Here + (EQ, LT) -> Down + (EQ, GT) -> Up + (LT, EQ) -> Right + (GT, EQ) -> Left + + (LT, LT) -> DownRight + (GT, LT) -> DownLeft + + (LT, GT) -> UpRight + (GT, GT) -> UpLeft + +-- | Take one (potentially diagonal) step towards the given position +-- +-- โ src tgt. isUnit (src `diffPositions` (src `stepTowards tgt`)) +stepTowards + :: Position -- ^ Source + -> Position -- ^ Target + -> Position +stepTowards (view _Position -> pโ) (view _Position -> pโ) + | pโ == pโ = _Position # pโ + | otherwise = + let (_:p:_) = line pโ pโ + in _Position # p + +-- | Newtype controlling arbitrary generation to only include cardinal +-- directions ('Up', 'Down', 'Left', 'Right') +newtype Cardinal = Cardinal { getCardinal :: Direction } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, Function, CoArbitrary) + deriving newtype (Opposite) + +instance Arbitrary Cardinal where + arbitrary = Cardinal <$> elements [Up, Down, Left, Right] + +-------------------------------------------------------------------------------- + +data Corner + = TopLeft + | TopRight + | BottomLeft + | BottomRight + deriving stock (Show, Eq, Ord, Enum, Bounded, Generic) + deriving Arbitrary via GenericArbitrary Corner + +instance Opposite Corner where + opposite TopLeft = BottomRight + opposite TopRight = BottomLeft + opposite BottomLeft = TopRight + opposite BottomRight = TopLeft + +data Edge + = TopEdge + | LeftEdge + | RightEdge + | BottomEdge + deriving stock (Show, Eq, Ord, Enum, Bounded, Generic) + deriving Arbitrary via GenericArbitrary Edge + +instance Opposite Edge where + opposite TopEdge = BottomEdge + opposite BottomEdge = TopEdge + opposite LeftEdge = RightEdge + opposite RightEdge = LeftEdge + +cornerEdges :: Corner -> (Edge, Edge) +cornerEdges TopLeft = (TopEdge, LeftEdge) +cornerEdges TopRight = (TopEdge, RightEdge) +cornerEdges BottomLeft = (BottomEdge, LeftEdge) +cornerEdges BottomRight = (BottomEdge, RightEdge) + +-------------------------------------------------------------------------------- + +data Neighbors a = Neighbors + { _topLeft + , _top + , _topRight + , _left + , _right + , _bottomLeft + , _bottom + , _bottomRight :: a + } + deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic) + deriving anyclass (NFData, CoArbitrary, Function, MonoFoldable) + deriving Arbitrary via GenericArbitrary (Neighbors a) + +type instance Element (Neighbors a) = a + +makeFieldsNoPrefix ''Neighbors + +instance Applicative Neighbors where + pure ฮฑ = Neighbors + { _topLeft = ฮฑ + , _top = ฮฑ + , _topRight = ฮฑ + , _left = ฮฑ + , _right = ฮฑ + , _bottomLeft = ฮฑ + , _bottom = ฮฑ + , _bottomRight = ฮฑ + } + nf <*> nx = Neighbors + { _topLeft = nf ^. topLeft $ nx ^. topLeft + , _top = nf ^. top $ nx ^. top + , _topRight = nf ^. topRight $ nx ^. topRight + , _left = nf ^. left $ nx ^. left + , _right = nf ^. right $ nx ^. right + , _bottomLeft = nf ^. bottomLeft $ nx ^. bottomLeft + , _bottom = nf ^. bottom $ nx ^. bottom + , _bottomRight = nf ^. bottomRight $ nx ^. bottomRight + } + +edges :: Neighbors a -> Edges a +edges neighs = Edges + { eTop = neighs ^. top + , eBottom = neighs ^. bottom + , eLeft = neighs ^. left + , eRight = neighs ^. right + } + +neighborDirections :: Neighbors Direction +neighborDirections = Neighbors + { _topLeft = UpLeft + , _top = Up + , _topRight = UpRight + , _left = Left + , _right = Right + , _bottomLeft = DownLeft + , _bottom = Down + , _bottomRight = DownRight + } + +neighborPositions :: Num a => Position' a -> Neighbors (Position' a) +neighborPositions pos = (`move` pos) <$> neighborDirections + +neighborCells :: Num a => V2 a -> Neighbors (V2 a) +neighborCells = map (view _Position) . neighborPositions . review _Position + +arrayNeighbors + :: (IArray a e, Ix i, Num i) + => a (V2 i) e + -> V2 i + -> Neighbors (Maybe e) +arrayNeighbors arr center = arrLookup <$> neighborPositions (_Position # center) + where + arrLookup (view _Position -> pos) + | inRange (bounds arr) pos = Just $ arr ! pos + | otherwise = Nothing + +-- | Returns a list of all 4 90-degree rotations of the given neighbors +rotations :: Neighbors a -> V4 (Neighbors a) +rotations orig@(Neighbors tl t tr l r bl b br) = V4 + orig -- tl t tr + -- l r + -- bl b br + + (Neighbors bl l tl b t br r tr) -- bl l tl + -- b t + -- br r tr + + (Neighbors br b bl r l tr t tl) -- br b bl + -- r l + -- tr t tl + + (Neighbors tr r br t b tl l bl) -- tr r br + -- t b + -- tl l bl + +-------------------------------------------------------------------------------- + +newtype Per a b = Rate Double + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON) + via Double + deriving (Semigroup, Monoid) via Product Double + deriving Show via ShowUnitSuffix (Per a b) Double +deriving via Double + instance ( Distribution d Double + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d (Per a b) + +instance (Unit a, Unit b) => Unit (a `Per` b) where + unitSuffix = unitSuffix @a <> "/" <> unitSuffix @b + +invertRate :: a `Per` b -> b `Per` a +invertRate (Rate p) = Rate $ 1 / p + +invertedRate :: Iso (a `Per` b) (b' `Per` a') (b `Per` a) (a' `Per` b') +invertedRate = iso invertRate invertRate + +type (:+:) :: Type -> Type -> Type +type family (:+:) a b where + a :+: a = a + a :+: (Uno b) = a + +infixl 6 |+| +class AddUnit a b where + (|+|) :: a -> b -> a :+: b + +instance Scalar a => AddUnit a a where + x' |+| y' = fromScalar $ scalar x' + scalar y' + +instance (Scalar a, Scalar b) => AddUnit a (Uno b) where + x' |+| y' = fromScalar $ scalar x' + scalar y' + +type (:*:) :: Type -> Type -> Type +type family (:*:) a b where + (a `Per` b) :*: b = a + (Square a) :*: a = Cubic a + a :*: a = Square a + a :*: Uno b = a + a :*: b = a :**: b + +infixl 7 |*| +class MulUnit a b where + (|*|) :: a -> b -> a :*: b + +instance (Scalar a, Scalar b) => MulUnit (a `Per` b) b where + (Rate rate) |*| b = fromScalar $ rate * scalar b + +instance forall a. (Scalar a, a :*: a ~ Square a) => MulUnit a a where + x' |*| y' = Square @a . fromScalar $ scalar x' * scalar y' + +instance forall a. (Scalar a) => MulUnit (Square a) a where + x' |*| y' = Cubic @a . fromScalar $ scalar x' * scalar y' + +instance {-# INCOHERENT #-} forall a b. + (Scalar a, Scalar b, Scalar (a :*: Uno b)) + => MulUnit a (Uno b) where + x' |*| y' = fromScalar $ scalar x' * scalar y' + +type (:/:) :: Type -> Type -> Type +type family (:/:) a b where + (Square a) :/: a = a + (Cubic a) :/: a = Square a + (Cubic a) :/: (Square a) = a + (a :**: b) :/: b = a + (a :**: b) :/: a = b + a :/: Uno b = a + a :/: b = a `Per` b + +infixl 7 |/| +class DivUnit a b where + (|/|) :: a -> b -> a :/: b + +instance Scalar a => DivUnit (Square a) a where + (Square a) |/| b = fromScalar $ scalar a / scalar b + +instance Scalar a => DivUnit (Cubic a) a where + (Cubic a) |/| b = fromScalar $ scalar a / scalar b + +instance (Scalar a, Cubic a :/: Square a ~ a) + => DivUnit (Cubic a) (Square a) where + (Cubic a) |/| (Square b) = fromScalar $ scalar a / scalar b + +instance (Scalar a, Scalar b) => DivUnit (a :**: b) b where + (Times a) |/| b = fromScalar $ scalar a / scalar b + +instance (Scalar a, Scalar b) => DivUnit (a :**: b) a where + (Times a) |/| b = fromScalar $ scalar a / scalar b + +instance {-# INCOHERENT #-} forall a b. + (Scalar a, Scalar b, Scalar (a :/: Uno b)) + => DivUnit a (Uno b) where + x' |/| y' = fromScalar $ scalar x' / scalar y' + +-- | Dimensionless quantitites (mass per unit mass, radians, etc) +-- +-- see <https://en.wikipedia.org/wiki/Parts-per_notation#Uno> +newtype Uno a = Uno a + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON + , Scalar, Show + ) + via a + deriving Unit via UnitSymbol "" (Uno a) + +newtype Square a = Square a + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON + , Scalar + ) + via a +deriving via (a :: Type) + instance ( Distribution d a + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d (Square a) + +instance Unit a => Unit (Square a) where + unitSuffix = unitSuffix @a <> "ยฒ" + +instance Show a => Show (Square a) where + show (Square n) = show n <> "ยฒ" + +squared :: (Scalar a, a :*: a ~ Square a) => a -> Square a +squared v = v |*| v + +newtype Cubic a = Cubic a + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON + , Scalar + ) + via a +deriving via (a :: Type) + instance ( Distribution d a + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d (Cubic a) + +instance Unit a => Unit (Cubic a) where + unitSuffix = unitSuffix @a <> "ยณ" + +instance Show a => Show (Cubic a) where + show (Cubic n) = show n <> "ยณ" + +newtype (:**:) a b = Times Double + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON) + via Double + deriving (Semigroup, Monoid) via Sum Double + deriving Show via ShowUnitSuffix (a :**: b) Double +deriving via Double + instance ( Distribution d Double + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d (a :**: b) + +instance (Unit a, Unit b) => Unit (a :**: b) where + unitSuffix = unitSuffix @a <> " " <> unitSuffix @b + +-------------------------------------------------------------------------------- + +newtype Ticks = Ticks Word + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (Num, Ord, Bounded, Enum, Integral, Real, ToJSON, FromJSON) via Word + deriving (Semigroup, Monoid) via (Sum Word) + deriving Scalar via ScalarIntegral Ticks + deriving Arbitrary via GenericArbitrary Ticks + deriving Unit via UnitSymbol "ticks" Ticks + deriving Show via ShowUnitSuffix Ticks Word +deriving via Word + instance ( Distribution d Word + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d Ticks + +newtype Tiles = Tiles Double + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (Num, Ord, Enum, Real, ToJSON, FromJSON, Scalar) via Double + deriving (Semigroup, Monoid) via (Sum Double) + deriving Arbitrary via GenericArbitrary Tiles + deriving Unit via UnitSymbol "m" Tiles + deriving Show via ShowUnitSuffix Tiles Double +deriving via Double + instance ( Distribution d Double + , forall xx yy. Coercible xx yy => Coercible (d xx) (d yy) + ) + => Distribution d Tiles + +type TicksPerTile = Ticks `Per` Tiles +type TilesPerTick = Tiles `Per` Ticks + +timesTiles :: TicksPerTile -> Tiles -> Ticks +timesTiles = (|*|) + +-- | Calculate the (cartesian) distance between two 'Position's, floored and +-- represented as a number of 'Tile's +-- +-- Note that this is imprecise, and may be different than the length of a +-- bresenham's line between the points +distance :: Position -> Position -> Tiles +distance + = (fromScalar .) . (Metric.distance `on` (fmap fromIntegral . view _Position)) + +-------------------------------------------------------------------------------- + +newtype Hitpoints = Hitpoints Word + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving ( Arbitrary, Num, Ord, Bounded, Enum, Integral, Real, Scalar + , ToJSON, FromJSON + ) + via Word + deriving (Semigroup, Monoid) via Sum Word + deriving Unit via UnitSymbol "hp" Hitpoints + deriving Show via ShowUnitSuffix Hitpoints Word + +-------------------------------------------------------------------------------- + +-- | Grams, the fundamental measure of weight in Xanthous. +newtype Grams = Grams Double + deriving stock (Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving ( Arbitrary, Num, Ord, Enum, Real, Floating, Fractional, RealFloat + , RealFrac, Scalar, ToJSON, FromJSON + ) + via Double + deriving (Semigroup, Monoid) via Sum Double + deriving Unit via UnitSymbol "g" Grams + deriving Show via ShowUnitSuffix Grams Double + +-- | Every tile is 1 meter +type Meters = Tiles + +-------------------------------------------------------------------------------- + +data Box a = Box + { _topLeftCorner :: V2 a + , _dimensions :: V2 a + } + deriving stock (Show, Eq, Ord, Functor, Generic) + deriving Arbitrary via GenericArbitrary (Box a) +makeFieldsNoPrefix ''Box + +bottomRightCorner :: Num a => Box a -> V2 a +bottomRightCorner box = + V2 (box ^. topLeftCorner . L._x + box ^. dimensions . L._x) + (box ^. topLeftCorner . L._y + box ^. dimensions . L._y) + +setBottomRightCorner :: (Num a, Ord a) => Box a -> V2 a -> Box a +setBottomRightCorner box br@(V2 brx bry) + | brx < box ^. topLeftCorner . L._x || bry < box ^. topLeftCorner . L._y + = box & topLeftCorner .~ br + & dimensions . L._x .~ ((box ^. topLeftCorner . L._x) - brx) + & dimensions . L._y .~ ((box ^. topLeftCorner . L._y) - bry) + | otherwise + = box & dimensions . L._x .~ (brx - (box ^. topLeftCorner . L._x)) + & dimensions . L._y .~ (bry - (box ^. topLeftCorner . L._y)) + +inBox :: (Ord a, Num a) => Box a -> V2 a -> Bool +inBox box pt = flip all [L._x, L._y] $ \component -> + between (box ^. topLeftCorner . component) + (box ^. to bottomRightCorner . component) + (pt ^. component) + +boxIntersects :: (Ord a, Num a) => Box a -> Box a -> Bool +boxIntersects boxโ boxโ + = any (inBox boxโ) [boxโ ^. topLeftCorner, bottomRightCorner boxโ] + +boxCenter :: (Fractional a) => Box a -> V2 a +boxCenter box = V2 cx cy + where + cx = box ^. topLeftCorner . L._x + (box ^. dimensions . L._x / 2) + cy = box ^. topLeftCorner . L._y + (box ^. dimensions . L._y / 2) + +boxEdge :: (Enum a, Num a) => Box a -> Edge -> [V2 a] +boxEdge box LeftEdge = + V2 (box ^. topLeftCorner . L._x) + <$> [box ^. topLeftCorner . L._y .. box ^. to bottomRightCorner . L._y] +boxEdge box RightEdge = + V2 (box ^. to bottomRightCorner . L._x) + <$> [box ^. to bottomRightCorner . L._y .. box ^. to bottomRightCorner . L._y] +boxEdge box TopEdge = + flip V2 (box ^. topLeftCorner . L._y) + <$> [box ^. topLeftCorner . L._x .. box ^. to bottomRightCorner . L._x] +boxEdge box BottomEdge = + flip V2 (box ^. to bottomRightCorner . L._y) + <$> [box ^. topLeftCorner . L._x .. box ^. to bottomRightCorner . L._x] diff --git a/users/grfn/xanthous/src/Xanthous/Data/App.hs b/users/grfn/xanthous/src/Xanthous/Data/App.hs new file mode 100644 index 000000000000..a2cfcb8001cb --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/App.hs @@ -0,0 +1,45 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.App + ( Panel(..) + , ResourceName(..) + , AppEvent(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Test.QuickCheck +import Test.QuickCheck.Instances.Text () +import Data.Aeson (ToJSON, FromJSON) +-------------------------------------------------------------------------------- +import Xanthous.Util.QuickCheck +-------------------------------------------------------------------------------- + +-- | Enum for "panels" displayed in the game's UI. +data Panel + = -- | A panel displaying the character's inventory + InventoryPanel + | -- | A panel describing an item in the inventory in detail + -- + -- The argument is the full description of the item + ItemDescriptionPanel Text + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function, ToJSON, FromJSON) + deriving Arbitrary via GenericArbitrary Panel + + +data ResourceName + = MapViewport -- ^ The main viewport where we display the game content + | Character -- ^ The character + | MessageBox -- ^ The box where we display messages to the user + | Prompt -- ^ The game's prompt + | Panel Panel -- ^ A panel in the game + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function, ToJSON, FromJSON) + deriving Arbitrary via GenericArbitrary ResourceName + +data AppEvent + = AutoContinue -- ^ Continue whatever autocommand has been requested by the + -- user + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function, ToJSON, FromJSON) + deriving Arbitrary via GenericArbitrary AppEvent diff --git a/users/grfn/xanthous/src/Xanthous/Data/Entities.hs b/users/grfn/xanthous/src/Xanthous/Data/Entities.hs new file mode 100644 index 000000000000..39953410f2f3 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/Entities.hs @@ -0,0 +1,68 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.Entities + ( -- * Collisions + Collision(..) + , _Stop + , _Combat + -- * Entity Attributes + , EntityAttributes(..) + , blocksVision + , blocksObject + , collision + , defaultEntityAttributes + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson (ToJSON(..), FromJSON(..), (.:?), (.!=), withObject) +import Data.Aeson.Generic.DerivingVia +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +import Test.QuickCheck +-------------------------------------------------------------------------------- + +data Collision + = Stop -- ^ Can't move through this + | Combat -- ^ Moving into this equates to hitting it with a stick + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Collision + deriving (ToJSON, FromJSON) + via WithOptions '[ AllNullaryToStringTag 'True ] + Collision +makePrisms ''Collision + +-- | Attributes of an entity +data EntityAttributes = EntityAttributes + { _blocksVision :: Bool + -- | Does this entity block a large object from being put in the same tile as + -- it - eg a a door being closed on it + , _blocksObject :: Bool + -- | What type of collision happens when moving into this entity? + , _collision :: Collision + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary EntityAttributes + deriving (ToJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + EntityAttributes +makeLenses ''EntityAttributes + +instance FromJSON EntityAttributes where + parseJSON = withObject "EntityAttributes" $ \o -> do + _blocksVision <- o .:? "blocksVision" + .!= _blocksVision defaultEntityAttributes + _blocksObject <- o .:? "blocksObject" + .!= _blocksObject defaultEntityAttributes + _collision <- o .:? "collision" + .!= _collision defaultEntityAttributes + pure EntityAttributes {..} + +defaultEntityAttributes :: EntityAttributes +defaultEntityAttributes = EntityAttributes + { _blocksVision = False + , _blocksObject = False + , _collision = Stop + } diff --git a/users/grfn/xanthous/src/Xanthous/Data/EntityChar.hs b/users/grfn/xanthous/src/Xanthous/Data/EntityChar.hs new file mode 100644 index 000000000000..855a3462daee --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/EntityChar.hs @@ -0,0 +1,56 @@ +{-# LANGUAGE RoleAnnotations #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityChar + ( EntityChar(..) + , HasChar(..) + , HasStyle(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding ((.=)) +-------------------------------------------------------------------------------- +import qualified Graphics.Vty.Attributes as Vty +import Test.QuickCheck +import Data.Aeson +-------------------------------------------------------------------------------- +import Xanthous.Orphans () +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +-------------------------------------------------------------------------------- + + +class HasChar s a | s -> a where + char :: Lens' s a + {-# MINIMAL char #-} + +data EntityChar = EntityChar + { _char :: Char + , _style :: Vty.Attr + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary EntityChar +makeFieldsNoPrefix ''EntityChar + +instance FromJSON EntityChar where + parseJSON (String (chr :< Empty)) = pure $ EntityChar chr Vty.defAttr + parseJSON (Object o) = do + (EntityChar _char _) <- o .: "char" + _style <- o .:? "style" .!= Vty.defAttr + pure EntityChar {..} + parseJSON _ = fail "Invalid type, expected string or object" + +instance ToJSON EntityChar where + toJSON (EntityChar chr styl) + | styl == Vty.defAttr = String $ chr <| Empty + | otherwise = object + [ "char" .= chr + , "style" .= styl + ] + +instance IsString EntityChar where + fromString [ch] = EntityChar ch Vty.defAttr + fromString _ = error "Entity char must only be a single character" diff --git a/users/grfn/xanthous/src/Xanthous/Data/EntityMap.hs b/users/grfn/xanthous/src/Xanthous/Data/EntityMap.hs new file mode 100644 index 000000000000..1d9c4d46cdc9 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/EntityMap.hs @@ -0,0 +1,277 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE DeriveFunctor #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityMap + ( EntityMap + , _EntityMap + , EntityID + , emptyEntityMap + , insertAt + , insertAtReturningID + , fromEIDsAndPositioned + , toEIDsAndPositioned + , atPosition + , atPositionWithIDs + , positions + , lookup + , lookupWithPosition + , positionOf + -- , positionedEntities + , neighbors + , Deduplicate(..) + + -- * debug + , byID + , byPosition + , lastID + + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (lookup) +import Xanthous.Data + ( Position + , Positioned(..) + , positioned + , Neighbors(..) + , neighborPositions, position + ) +import Xanthous.Data.VectorBag +import Xanthous.Orphans () +import Xanthous.Util (EqEqProp(..)) +-------------------------------------------------------------------------------- +import Data.Monoid (Endo(..)) +import Test.QuickCheck (Arbitrary(..), CoArbitrary, Function) +import Test.QuickCheck.Checkers (EqProp) +import Test.QuickCheck.Instances.UnorderedContainers () +import Test.QuickCheck.Instances.Vector () +import Text.Show (showString, showParen) +import Data.Aeson +-------------------------------------------------------------------------------- + +type EntityID = Word32 +type NonNullSet a = NonNull (Set a) + +data EntityMap a where + EntityMap :: + { _byPosition :: Map Position (NonNullSet EntityID) + , _byID :: HashMap EntityID (Positioned a) + , _lastID :: EntityID + } -> EntityMap a + deriving stock (Functor, Foldable, Traversable, Generic) + deriving anyclass (NFData, CoArbitrary, Function) +deriving via (EqEqProp (EntityMap a)) instance (Eq a, Ord a) => EqProp (EntityMap a) +makeLenses ''EntityMap + +instance ToJSON a => ToJSON (EntityMap a) where + toJSON = toJSON . toEIDsAndPositioned + + +instance FromJSON a => FromJSON (EntityMap a) where + parseJSON = fmap (fromEIDsAndPositioned @[_]) . parseJSON + +byIDInvariantError :: forall a. a +byIDInvariantError = error $ "Invariant violation: All EntityIDs in byPosition " + <> "must point to entityIDs in byID" + +instance (Ord a, Eq a) => Eq (EntityMap a) where + -- emโ == emโ = emโ ^. _EntityMap == emโ ^. _EntityMap + (==) = (==) `on` view (_EntityMap . to sort) + +deriving stock instance (Ord a) => Ord (EntityMap a) + +instance Show a => Show (EntityMap a) where + showsPrec pr em + = showParen (pr > 10) + $ showString + . ("fromEIDsAndPositioned " <>) + . show + . toEIDsAndPositioned + $ em + +instance Arbitrary a => Arbitrary (EntityMap a) where + arbitrary = review _EntityMap <$> arbitrary + shrink em = review _EntityMap <$> shrink (em ^. _EntityMap) + +type instance Index (EntityMap a) = EntityID +type instance IxValue (EntityMap a) = (Positioned a) +instance Ixed (EntityMap a) where ix eid = at eid . traverse + +instance At (EntityMap a) where + at eid = lens (view $ byID . at eid) setter + where + setter :: EntityMap a -> Maybe (Positioned a) -> EntityMap a + setter m Nothing = fromMaybe m $ do + Positioned pos _ <- m ^. byID . at eid + pure $ m + & removeEIDAtPos pos + & byID . at eid .~ Nothing + setter m (Just pe@(Positioned pos _)) = m + & (case lookupWithPosition eid m of + Nothing -> id + Just (Positioned origPos _) -> removeEIDAtPos origPos + ) + & byID . at eid ?~ pe + & byPosition . at pos %~ \case + Nothing -> Just $ opoint eid + Just es -> Just $ ninsertSet eid es + removeEIDAtPos pos = + byPosition . at pos %~ (>>= fromNullable . ndeleteSet eid) + +instance Semigroup (EntityMap a) where + emโ <> emโ = alaf Endo foldMap (uncurry insertAt) (emโ ^. _EntityMap) emโ + +instance Monoid (EntityMap a) where + mempty = emptyEntityMap + +instance FunctorWithIndex EntityID EntityMap + +instance FoldableWithIndex EntityID EntityMap + +instance TraversableWithIndex EntityID EntityMap where + itraversed = byID . itraversed . rmap sequenceA . distrib + itraverse = itraverseOf itraversed + +type instance Element (EntityMap a) = a +instance MonoFoldable (EntityMap a) + +emptyEntityMap :: EntityMap a +emptyEntityMap = EntityMap mempty mempty 0 + +newtype Deduplicate a = Deduplicate (EntityMap a) + deriving stock (Show, Traversable, Generic) + deriving newtype (Eq, Functor, Foldable, EqProp, Arbitrary) + +instance Semigroup (Deduplicate a) where + (Deduplicate emโ) <> (Deduplicate emโ) = + let _byID = emโ ^. byID <> emโ ^. byID + _byPosition = mempty &~ do + ifor_ _byID $ \eid (Positioned pos _) -> + at pos %= \case + Just eids -> Just $ ninsertSet eid eids + Nothing -> Just $ opoint eid + _lastID = fromMaybe 1 $ maximumOf (ifolded . asIndex) _byID + in Deduplicate EntityMap{..} + + +-------------------------------------------------------------------------------- + +_EntityMap :: Iso' (EntityMap a) [(Position, a)] +_EntityMap = iso hither yon + where + hither :: EntityMap a -> [(Position, a)] + hither em = do + (pos, eids) <- em ^. byPosition . _Wrapped + eid <- toList eids + ent <- em ^.. byID . at eid . folded . positioned + pure (pos, ent) + yon :: [(Position, a)] -> EntityMap a + yon poses = alaf Endo foldMap (uncurry insertAt) poses emptyEntityMap + + +insertAtReturningID :: forall a. Position -> a -> EntityMap a -> (EntityID, EntityMap a) +insertAtReturningID pos e em = + let (eid, em') = em & lastID <+~ 1 + in em' + & byID . at eid ?~ Positioned pos e + & byPosition . at pos %~ \case + Nothing -> Just $ opoint eid + Just es -> Just $ ninsertSet eid es + & (eid, ) + +insertAt :: forall a. Position -> a -> EntityMap a -> EntityMap a +insertAt pos e = snd . insertAtReturningID pos e + +atPosition :: forall a. (Ord a, Show a) => Position -> Lens' (EntityMap a) (VectorBag a) +atPosition pos = lens getter setter + where + getter em = + let eids :: VectorBag EntityID + eids = maybe mempty (VectorBag . toVector . toNullable) + $ em ^. byPosition . at pos + in getEIDAssume em <$> eids + setter em Empty = em & byPosition . at pos .~ Nothing + setter em (sort -> entities) = + let origEIDs = maybe Empty toNullable $ em ^. byPosition . at pos + origEntitiesWithIDs = + sortOn snd $ toList origEIDs <&> \eid -> (eid, getEIDAssume em eid) + go allesโ@((eid, eโ) :< esโ) -- orig + (eโ :< esโ) -- new + | eโ == eโ + -- same, do nothing + = let (eids, lastEID, byID') = go esโ esโ + in (insertSet eid eids, lastEID, byID') + | otherwise + -- eโ is new, generate a new ID for it + = let (eids, lastEID, byID') = go allesโ esโ + eid' = succ lastEID + in (insertSet eid' eids, eid', byID' & at eid' ?~ Positioned pos eโ) + go Empty Empty = (mempty, em ^. lastID, em ^. byID) + go orig Empty = + let byID' = foldr deleteMap (em ^. byID) $ map fst orig + in (mempty, em ^. lastID, byID') + go Empty (new :< news) = + let (eids, lastEID, byID') = go Empty news + eid' = succ lastEID + in (insertSet eid' eids, eid', byID' & at eid' ?~ Positioned pos new) + go _ _ = error "unreachable" + (eidsAtPosition, newLastID, newByID) = go origEntitiesWithIDs entities + in em & byPosition . at pos .~ fromNullable eidsAtPosition + & byID .~ newByID + & lastID .~ newLastID + +getEIDAssume :: EntityMap a -> EntityID -> a +getEIDAssume em eid = fromMaybe byIDInvariantError + $ em ^? byID . ix eid . positioned + +atPositionWithIDs :: Position -> EntityMap a -> Vector (EntityID, Positioned a) +atPositionWithIDs pos em = + let eids = maybe mempty (toVector . toNullable) + $ em ^. byPosition . at pos + in (id &&& Positioned pos . getEIDAssume em) <$> eids + +fromEIDsAndPositioned + :: forall mono a. (MonoFoldable mono, Element mono ~ (EntityID, Positioned a)) + => mono + -> EntityMap a +fromEIDsAndPositioned eps = newLastID $ alaf Endo foldMap insert' eps mempty + where + insert' (eid, pe@(Positioned pos _)) + = (byID . at eid ?~ pe) + . (byPosition . at pos %~ \case + Just eids -> Just $ ninsertSet eid eids + Nothing -> Just $ opoint eid + ) + newLastID em = em & lastID + .~ fromMaybe 1 + (maximumOf (ifolded . asIndex) (em ^. byID)) + +toEIDsAndPositioned :: EntityMap a -> [(EntityID, Positioned a)] +toEIDsAndPositioned = itoListOf $ byID . ifolded + +positions :: EntityMap a -> [Position] +positions = toListOf $ byPosition . to keys . folded + +lookupWithPosition :: EntityID -> EntityMap a -> Maybe (Positioned a) +lookupWithPosition eid = view $ byID . at eid + +lookup :: EntityID -> EntityMap a -> Maybe a +lookup eid = fmap (view positioned) . lookupWithPosition eid + +-- unlawful :( +-- positionedEntities :: IndexedTraversal EntityID (EntityMap a) (EntityMap b) (Positioned a) (Positioned b) +-- positionedEntities = byID . itraversed + +neighbors :: (Ord a, Show a) => Position -> EntityMap a -> Neighbors (VectorBag a) +neighbors pos em = (\p -> view (atPosition p) em) <$> neighborPositions pos + +-- | Traversal to the position of the entity with the given ID +positionOf :: EntityID -> Traversal' (EntityMap a) Position +positionOf eid = ix eid . position + +-------------------------------------------------------------------------------- +makeWrapped ''Deduplicate diff --git a/users/grfn/xanthous/src/Xanthous/Data/EntityMap/Graphics.hs b/users/grfn/xanthous/src/Xanthous/Data/EntityMap/Graphics.hs new file mode 100644 index 000000000000..1398c611cf20 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/EntityMap/Graphics.hs @@ -0,0 +1,72 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityMap.Graphics + ( visiblePositions + , visibleEntities + , lineOfSight + , linesOfSight + , canSee + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (lines) +-------------------------------------------------------------------------------- +import Xanthous.Util (takeWhileInclusive) +import Xanthous.Data +import Xanthous.Data.Entities +import Xanthous.Data.EntityMap +import Xanthous.Game.State +import Xanthous.Util.Graphics (circle, line) +-------------------------------------------------------------------------------- + +-- | Returns a set of positions that are visible, when taking into account +-- 'blocksVision', from the given position, within the given radius. +visiblePositions + :: Entity e + => Position + -> Word -- ^ Vision radius + -> EntityMap e + -> Set Position +visiblePositions pos radius + = setFromList . positions . visibleEntities pos radius + +-- | Returns a list of entities on the *line of sight* from the first position +-- to the second position +lineOfSight + :: forall e. Entity e + => Position -- ^ Origin + -> Position -- ^ Destination + -> EntityMap e + -> [(Position, Vector (EntityID, e))] +lineOfSight (view _Position -> origin) (view _Position -> destination) em = + takeWhileInclusive (none (view blocksVision . entityAttributes . snd) . snd) + $ getPositionedAt <$> line origin destination + where + getPositionedAt :: V2 Int -> (Position, Vector (EntityID, e)) + getPositionedAt (review _Position -> p) = + (p, over _2 (view positioned) <$> atPositionWithIDs p em) + +-- | Returns a list of individual lines of sight, each of which is a list of +-- entities at positions on that line of sight +linesOfSight + :: forall e. Entity e + => Position -- ^ Centerpoint + -> Word -- ^ Radius + -> EntityMap e + -> [[(Position, Vector (EntityID, e))]] +linesOfSight pos visionRadius em = + radius <&> \edge -> lineOfSight pos (_Position # edge) em + where + radius = circle (pos ^. _Position) $ fromIntegral visionRadius + +-- | Given a point and a radius of vision, returns a list of all entities that +-- are *visible* (eg, not blocked by an entity that obscures vision) from that +-- point +visibleEntities :: Entity e => Position -> Word -> EntityMap e -> EntityMap e +visibleEntities pos visionRadius + = fromEIDsAndPositioned + . foldMap (\(p, es) -> over _2 (Positioned p) <$> es) + . fold + . linesOfSight pos visionRadius + +canSee :: Entity e => (e -> Bool) -> Position -> Word -> EntityMap e -> Bool +canSee match pos radius = any match . visibleEntities pos radius +-- ^ this might be optimizable diff --git a/users/grfn/xanthous/src/Xanthous/Data/Levels.hs b/users/grfn/xanthous/src/Xanthous/Data/Levels.hs new file mode 100644 index 000000000000..13251d8afdf2 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/Levels.hs @@ -0,0 +1,180 @@ +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.Levels + ( Levels + , allLevels + , numLevels + , nextLevel + , prevLevel + , mkLevels1 + , mkLevels + , oneLevel + , current + , ComonadStore(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding ((<.>), Empty, foldMap) +import Xanthous.Util (between, EqProp, EqEqProp(..)) +import Xanthous.Util.Comonad (current) +import Xanthous.Orphans () +-------------------------------------------------------------------------------- +import Control.Comonad.Store +import Control.Comonad.Store.Zipper +import Data.Aeson (ToJSON(..), FromJSON(..)) +import Data.Aeson.Generic.DerivingVia +import Data.Functor.Apply +import Data.Foldable (foldMap) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as NE +import Data.Maybe (fromJust) +import Data.Sequence (Seq((:<|), Empty)) +import Data.Semigroup.Foldable.Class +import Data.Text (replace) +import Test.QuickCheck +-------------------------------------------------------------------------------- + +-- | Collection of levels plus a pointer to the current level +-- +-- Navigation is via the 'Comonad' instance. We can get the current level with +-- 'extract': +-- +-- extract @Levels :: Levels level -> level +-- +-- For access to and modification of the level, use +-- 'Xanthous.Util.Comonad.current' +newtype Levels a = Levels { levelZipper :: Zipper Seq a } + deriving stock (Generic) + deriving (Functor, Comonad, Foldable) via (Zipper Seq) + +type instance Element (Levels a) = a +instance MonoFoldable (Levels a) +instance MonoFunctor (Levels a) +instance MonoTraversable (Levels a) + +instance ComonadStore Word Levels where + pos = toEnum . pos . levelZipper + peek i = peek (fromEnum i) . levelZipper + +instance Traversable Levels where + traverse f (Levels z) = Levels <$> traverse f z + +instance Foldable1 Levels + +instance Traversable1 Levels where + traverse1 f levs@(Levels z) = seek (pos levs) . partialMkLevels <$> go (unzipper z) + where + go Empty = error "empty seq, unreachable" + go (x :<| xs) = (<|) <$> f x <.> go xs + +-- | Always takes the position of the latter element +instance Semigroup (Levels a) where + levsโ <> levsโ + = seek (pos levsโ) + . partialMkLevels + $ allLevels levsโ <> allLevels levsโ + +-- | The number of levels stored in 'Levels' +-- +-- Equivalent to 'Data.Foldable.length', but likely faster +numLevels :: Levels a -> Word +numLevels = toEnum . size . levelZipper + +-- | Make Levels from a Seq. Throws an error if the seq is not empty +partialMkLevels :: Seq a -> Levels a +partialMkLevels = Levels . fromJust . zipper + +-- | Make Levels from a possibly-empty structure +mkLevels :: Foldable1 f => f level -> Maybe (Levels level) +mkLevels = fmap Levels . zipper . foldMap pure + +-- | Make Levels from a non-empty structure +mkLevels1 :: Foldable1 f => f level -> Levels level +mkLevels1 = fromJust . mkLevels + +oneLevel :: a -> Levels a +oneLevel = mkLevels1 . Identity + +-- | Get a sequence of all the levels +allLevels :: Levels a -> Seq a +allLevels = unzipper . levelZipper + +-- | Step to the next level, generating a new level if necessary using the given +-- applicative action +nextLevel + :: Applicative m + => m level -- ^ Generate a new level, if necessary + -> Levels level + -> m (Levels level) +nextLevel genLevel levs + | succ (pos levs) < numLevels levs + = pure $ seeks succ levs + | otherwise + = genLevel <&> \level -> + seek (pos levs + 1) . partialMkLevels $ allLevels levs |> level + +-- | Go to the previous level. Returns Nothing if 'pos' is 0 +prevLevel :: Levels level -> Maybe (Levels level) +prevLevel levs | pos levs == 0 = Nothing + | otherwise = Just $ seeks pred levs + +-------------------------------------------------------------------------------- + +-- | alternate, slower representation of Levels we can Iso into to perform +-- various operations +data AltLevels a = AltLevels + { _levels :: NonEmpty a + , _currentLevel :: Word -- ^ invariant: is within the bounds of _levels + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + (AltLevels a) +makeLenses ''AltLevels + +alt :: Iso (Levels a) (Levels b) (AltLevels a) (AltLevels b) +alt = iso hither yon + where + hither levs = AltLevels (NE.fromList . toList $ allLevels levs) (pos levs) + yon (AltLevels levs curr) = seek curr $ mkLevels1 levs + +instance Eq a => Eq (Levels a) where + (==) = (==) `on` view alt + +deriving via EqEqProp (Levels a) instance Eq a => EqProp (Levels a) + +instance Show a => Show (Levels a) where + show = unpack . replace "AltLevels" "Levels" . pack . show . view alt + +instance NFData a => NFData (Levels a) where + rnf = rnf . view alt + +instance ToJSON a => ToJSON (Levels a) where + toJSON = toJSON . view alt + +instance FromJSON a => FromJSON (Levels a) where + parseJSON = fmap (review alt) . parseJSON + +instance Arbitrary a => Arbitrary (AltLevels a) where + arbitrary = do + _levels <- arbitrary + _currentLevel <- choose (0, pred . toEnum . length $ _levels) + pure AltLevels {..} + shrink als = do + _levels <- shrink $ als ^. levels + _currentLevel <- filter (between 0 $ pred . toEnum . length $ _levels) + $ shrink $ als ^. currentLevel + pure AltLevels {..} + + +instance Arbitrary a => Arbitrary (Levels a) where + arbitrary = review alt <$> arbitrary + shrink = fmap (review alt) . shrink . view alt + +instance CoArbitrary a => CoArbitrary (Levels a) where + coarbitrary = coarbitrary . view alt + +instance Function a => Function (Levels a) where + function = functionMap (view alt) (review alt) diff --git a/users/grfn/xanthous/src/Xanthous/Data/Memo.hs b/users/grfn/xanthous/src/Xanthous/Data/Memo.hs new file mode 100644 index 000000000000..2b2ee0f96028 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/Memo.hs @@ -0,0 +1,98 @@ +-------------------------------------------------------------------------------- +-- | Memoized values +-------------------------------------------------------------------------------- +module Xanthous.Data.Memo + ( Memoized(UnMemoized) + , memoizeWith + , getMemoized + , runMemoized + , fillWith + , fillWithM + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Data.Aeson (FromJSON, ToJSON) +import Test.QuickCheck (Arbitrary (arbitrary), oneof, CoArbitrary, Function) +import Test.QuickCheck.Checkers (EqProp) +import Xanthous.Util (EqEqProp(EqEqProp)) +import Control.Monad.State.Class (MonadState) +-------------------------------------------------------------------------------- + +-- | A memoized value, keyed by a key +-- +-- If key is different than what is stored here, then val is invalid +data Memoized key val = Memoized key val | UnMemoized + deriving stock (Show, Eq, Generic) + deriving anyclass (Hashable, FromJSON, ToJSON, NFData, CoArbitrary, Function) + deriving EqProp via EqEqProp (Memoized key val) + +instance (Arbitrary k, Arbitrary v) => Arbitrary (Memoized k v) where + arbitrary = oneof [ pure UnMemoized + , Memoized <$> arbitrary <*> arbitrary + ] + +-- | Construct a memoized value with the given key +memoizeWith :: forall key val. key -> val -> Memoized key val +memoizeWith = Memoized +{-# INLINE memoizeWith #-} + +-- | Retrieve a memoized value providing the key. If the value is unmemoized or +-- the keys do not match, returns Nothing. +-- +-- >>> getMemoized 1 (memoizeWith @Int @Int 1 2) +-- Just 2 +-- +-- >>> getMemoized 2 (memoizeWith @Int @Int 1 2) +-- Nothing +-- +-- >>> getMemoized 1 (UnMemoized :: Memoized Int Int) +-- Nothing +getMemoized :: Eq key => key -> Memoized key val -> Maybe val +getMemoized key (Memoized key' v) + | key == key' = Just v + | otherwise = Nothing +getMemoized _ UnMemoized = Nothing +{-# INLINE getMemoized #-} + +-- | Get a memoized value using an applicative action to obtain the key +runMemoized + :: (Eq key, Applicative m) + => Memoized key val + -> m key + -> m (Maybe val) +runMemoized m mk = getMemoized <$> mk <*> pure m + +-- | In a monadic state containing a 'MemoState', look up the current memoized +-- target of some lens keyed by k, filling it with v if not present and +-- returning either the new or old value +fillWith + :: forall m s k v. + (MonadState s m, Eq k) + => Lens' s (Memoized k v) + -> k + -> v + -> m v +fillWith l k v' = do + uses l (getMemoized k) >>= \case + Just v -> pure v + Nothing -> do + l .= memoizeWith k v' + pure v' + +-- | In a monadic state, look up the current memoized target of some lens keyed +-- by k, filling it with the result of some monadic action v if not present and +-- returning either the new or old value +fillWithM + :: forall m s k v. + (MonadState s m, Eq k) + => Lens' s (Memoized k v) + -> k + -> m v + -> m v +fillWithM l k mv = do + uses l (getMemoized k) >>= \case + Just v -> pure v + Nothing -> do + v' <- mv + l .= memoizeWith k v' + pure v' diff --git a/users/grfn/xanthous/src/Xanthous/Data/NestedMap.hs b/users/grfn/xanthous/src/Xanthous/Data/NestedMap.hs new file mode 100644 index 000000000000..1b875d448302 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/NestedMap.hs @@ -0,0 +1,227 @@ +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE PolyKinds #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.NestedMap + ( NestedMapVal(..) + , NestedMap(..) + , lookup + , lookupVal + , insert + + -- * + , (:->) + , BifunctorFunctor'(..) + , BifunctorMonad'(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (lookup, foldMap) +import qualified Xanthous.Prelude as P +-------------------------------------------------------------------------------- +import Test.QuickCheck +import Data.Aeson +import Data.Function (fix) +import Data.Foldable (Foldable(..)) +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NE +-------------------------------------------------------------------------------- + +-- | Natural transformations on bifunctors +type (:->) p q = forall a b. p a b -> q a b +infixr 0 :-> + +class (forall b. Bifunctor b => Bifunctor (t b)) => BifunctorFunctor' t where + bifmap' :: (Bifunctor p, Bifunctor q) => (p :-> q) -> t p :-> t q + +class BifunctorFunctor' t => BifunctorMonad' t where + bireturn' :: (Bifunctor p) => p :-> t p + + bibind' :: (Bifunctor p, Bifunctor q) => (p :-> t q) -> t p :-> t q + bibind' f = bijoin' . bifmap' f + + bijoin' :: (Bifunctor p) => t (t p) :-> t p + bijoin' = bibind' id + + {-# MINIMAL bireturn', (bibind' | bijoin') #-} + +-------------------------------------------------------------------------------- + +data NestedMapVal m k v = Val v | Nested (NestedMap m k v) + +deriving stock instance + ( forall k' v'. (Show k', Show v') => Show (m k' v') + , Show k + , Show v + ) => Show (NestedMapVal m k v) + +deriving stock instance + ( forall k' v'. (Eq k', Eq v') => Eq (m k' v') + , Eq k + , Eq v + ) => Eq (NestedMapVal m k v) + +instance + forall m k v. + ( Arbitrary (m k v) + , Arbitrary (m k (NestedMapVal m k v)) + , Arbitrary k + , Arbitrary v + , IsMap (m k (NestedMapVal m k v)) + , MapValue (m k (NestedMapVal m k v)) ~ (NestedMapVal m k v) + , ContainerKey (m k (NestedMapVal m k v)) ~ k + ) => Arbitrary (NestedMapVal m k v) where + arbitrary = sized . fix $ \gen n -> + let nst = fmap (NestedMap . mapFromList) + . listOf + $ (,) <$> arbitrary @k <*> gen (n `div` 2) + in if n == 0 + then Val <$> arbitrary + else oneof [ Val <$> arbitrary + , Nested <$> nst] + shrink (Val v) = Val <$> shrink v + shrink (Nested mkv) = Nested <$> shrink mkv + +instance Functor (m k) => Functor (NestedMapVal m k) where + fmap f (Val v) = Val $ f v + fmap f (Nested m) = Nested $ fmap f m + +instance Bifunctor m => Bifunctor (NestedMapVal m) where + bimap _ g (Val v) = Val $ g v + bimap f g (Nested m) = Nested $ bimap f g m + +instance BifunctorFunctor' NestedMapVal where + bifmap' _ (Val v) = Val v + bifmap' f (Nested m) = Nested $ bifmap' f m + +instance (ToJSONKey k, ToJSON v, ToJSON (m k (NestedMapVal m k v))) + => ToJSON (NestedMapVal m k v) where + toJSON (Val v) = toJSON v + toJSON (Nested m) = toJSON m + +instance Foldable (m k) => Foldable (NestedMapVal m k) where + foldMap f (Val v) = f v + foldMap f (Nested m) = foldMap f m + +-- _NestedMapVal +-- :: forall m k v m' k' v'. +-- ( IsMap (m k v), IsMap (m' k' v') +-- , IsMap (m [k] v), IsMap (m' [k'] v') +-- , ContainerKey (m k v) ~ k, ContainerKey (m' k' v') ~ k' +-- , ContainerKey (m [k] v) ~ [k], ContainerKey (m' [k'] v') ~ [k'] +-- , MapValue (m k v) ~ v, MapValue (m' k' v') ~ v' +-- , MapValue (m [k] v) ~ v, MapValue (m' [k'] v') ~ v' +-- ) +-- => Iso (NestedMapVal m k v) +-- (NestedMapVal m' k' v') +-- (m [k] v) +-- (m' [k'] v') +-- _NestedMapVal = iso hither yon +-- where +-- hither :: NestedMapVal m k v -> m [k] v +-- hither (Val v) = singletonMap [] v +-- hither (Nested m) = bimap _ _ $ m ^. _NestedMap +-- yon = _ + +-------------------------------------------------------------------------------- + +newtype NestedMap m k v = NestedMap (m k (NestedMapVal m k v)) + +deriving stock instance + ( forall k' v'. (Eq k', Eq v') => Eq (m k' v') + , Eq k + , Eq v + ) => Eq (NestedMap m k v) + +deriving stock instance + ( forall k' v'. (Show k', Show v') => Show (m k' v') + , Show k + , Show v + ) => Show (NestedMap m k v) + +instance Arbitrary (m k (NestedMapVal m k v)) + => Arbitrary (NestedMap m k v) where + arbitrary = NestedMap <$> arbitrary + shrink (NestedMap m) = NestedMap <$> shrink m + +instance Functor (m k) => Functor (NestedMap m k) where + fmap f (NestedMap m) = NestedMap $ fmap (fmap f) m + +instance Bifunctor m => Bifunctor (NestedMap m) where + bimap f g (NestedMap m) = NestedMap $ bimap f (bimap f g) m + +instance BifunctorFunctor' NestedMap where + bifmap' f (NestedMap m) = NestedMap . f $ bimap id (bifmap' f) m + +instance (ToJSONKey k, ToJSON v, ToJSON (m k (NestedMapVal m k v))) + => ToJSON (NestedMap m k v) where + toJSON (NestedMap m) = toJSON m + +instance Foldable (m k) => Foldable (NestedMap m k) where + foldMap f (NestedMap m) = foldMap (foldMap f) m + +-------------------------------------------------------------------------------- + +lookup + :: ( IsMap (m k (NestedMapVal m k v)) + , MapValue (m k (NestedMapVal m k v)) ~ (NestedMapVal m k v) + , ContainerKey (m k (NestedMapVal m k v)) ~ k + ) + => NonEmpty k + -> NestedMap m k v + -> Maybe (NestedMapVal m k v) +lookup (p :| []) (NestedMap vs) = P.lookup p vs +lookup (p :| (pโ : ps)) (NestedMap vs) = P.lookup p vs >>= \case + (Val _) -> Nothing + (Nested vs') -> lookup (pโ :| ps) vs' + +lookupVal + :: ( IsMap (m k (NestedMapVal m k v)) + , MapValue (m k (NestedMapVal m k v)) ~ (NestedMapVal m k v) + , ContainerKey (m k (NestedMapVal m k v)) ~ k + ) + => NonEmpty k + -> NestedMap m k v + -> Maybe v +lookupVal ks m + | Just (Val v) <- lookup ks m = Just v + | otherwise = Nothing + +insert + :: ( IsMap (m k (NestedMapVal m k v)) + , MapValue (m k (NestedMapVal m k v)) ~ (NestedMapVal m k v) + , ContainerKey (m k (NestedMapVal m k v)) ~ k + ) + => NonEmpty k + -> v + -> NestedMap m k v + -> NestedMap m k v +insert (k :| []) v (NestedMap m) = NestedMap $ P.insertMap k (Val v) m +insert (kโ :| (kโ : ks)) v (NestedMap m) = NestedMap $ alterMap upd kโ m + where + upd (Just (Nested nm)) = Just . Nested $ insert (kโ :| ks) v nm + upd _ = Just $ + let (kฮฉ :| ks') = NE.reverse (kโ :| ks) + in P.foldl' + (\m' k -> Nested . NestedMap . singletonMap k $ m') + (Nested . NestedMap . singletonMap kฮฉ $ Val v) + ks' + +-- _NestedMap +-- :: ( IsMap (m k v), IsMap (m' k' v') +-- , IsMap (m (NonEmpty k) v), IsMap (m' (NonEmpty k') v') +-- , ContainerKey (m k v) ~ k, ContainerKey (m' k' v') ~ k' +-- , ContainerKey (m (NonEmpty k) v) ~ (NonEmpty k) +-- , ContainerKey (m' (NonEmpty k') v') ~ (NonEmpty k') +-- , MapValue (m k v) ~ v, MapValue (m' k' v') ~ v' +-- , MapValue (m (NonEmpty k) v) ~ v, MapValue (m' (NonEmpty k') v') ~ v' +-- ) +-- => Iso (NestedMap m k v) +-- (NestedMap m' k' v') +-- (m (NonEmpty k) v) +-- (m' (NonEmpty k') v') +-- _NestedMap = iso undefined yon +-- where +-- hither (NestedMap m) = undefined . mapToList $ m +-- yon mkv = undefined diff --git a/users/grfn/xanthous/src/Xanthous/Data/VectorBag.hs b/users/grfn/xanthous/src/Xanthous/Data/VectorBag.hs new file mode 100644 index 000000000000..2e6d48062a45 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Data/VectorBag.hs @@ -0,0 +1,100 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.VectorBag + (VectorBag(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Data.Aeson +import qualified Data.Vector as V +import Test.QuickCheck +import Test.QuickCheck.Instances.Vector () +-------------------------------------------------------------------------------- + +-- | Acts exactly like a Vector, except ignores order when testing for equality +newtype VectorBag a = VectorBag (Vector a) + deriving stock + ( Traversable + , Generic + ) + deriving newtype + ( Show + , Read + , Foldable + , FromJSON + , FromJSON1 + , ToJSON + , Reversing + , Applicative + , Functor + , Monad + , Monoid + , Semigroup + , Arbitrary + , CoArbitrary + , Filterable + ) +makeWrapped ''VectorBag + +instance Function a => Function (VectorBag a) where + function = functionMap (\(VectorBag v) -> v) VectorBag + +type instance Element (VectorBag a) = a +deriving via (Vector a) instance MonoFoldable (VectorBag a) +deriving via (Vector a) instance GrowingAppend (VectorBag a) +deriving via (Vector a) instance SemiSequence (VectorBag a) +deriving via (Vector a) instance MonoPointed (VectorBag a) +deriving via (Vector a) instance MonoFunctor (VectorBag a) + +instance Cons (VectorBag a) (VectorBag b) a b where + _Cons = prism (\(x, VectorBag xs) -> VectorBag $ x <| xs) $ \(VectorBag v) -> + if V.null v + then Left (VectorBag mempty) + else Right (V.unsafeHead v, VectorBag $ V.unsafeTail v) + +instance AsEmpty (VectorBag a) where + _Empty = prism' (const $ VectorBag Empty) $ \case + (VectorBag Empty) -> Just () + _ -> Nothing + +instance Witherable VectorBag where + wither f (VectorBag v) = VectorBag <$> wither f v + witherM f (VectorBag v) = VectorBag <$> witherM f v + filterA p (VectorBag v) = VectorBag <$> filterA p v + +{- + TODO: + , Ixed + , FoldableWithIndex + , FunctorWithIndex + , TraversableWithIndex + , Snoc + , Each +-} + +instance Ord a => Eq (VectorBag a) where + (==) = (==) `on` (view _Wrapped . sort) + +instance Ord a => Ord (VectorBag a) where + compare = compare `on` (view _Wrapped . sort) + +instance MonoTraversable (VectorBag a) where + otraverse f (VectorBag v) = VectorBag <$> otraverse f v + +instance IsSequence (VectorBag a) where + fromList = VectorBag . fromList + break prd (VectorBag v) = bimap VectorBag VectorBag $ break prd v + span prd (VectorBag v) = bimap VectorBag VectorBag $ span prd v + dropWhile prd (VectorBag v) = VectorBag $ dropWhile prd v + takeWhile prd (VectorBag v) = VectorBag $ takeWhile prd v + splitAt idx (VectorBag v) = bimap VectorBag VectorBag $ splitAt idx v + unsafeSplitAt idx (VectorBag v) = + bimap VectorBag VectorBag $ unsafeSplitAt idx v + take n (VectorBag v) = VectorBag $ take n v + unsafeTake n (VectorBag v) = VectorBag $ unsafeTake n v + drop n (VectorBag v) = VectorBag $ drop n v + unsafeDrop n (VectorBag v) = VectorBag $ unsafeDrop n v + partition p (VectorBag v) = bimap VectorBag VectorBag $ partition p v diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Character.hs b/users/grfn/xanthous/src/Xanthous/Entities/Character.hs new file mode 100644 index 000000000000..d405cb40d3eb --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Character.hs @@ -0,0 +1,240 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Character + + ( -- * Character datatype + Character(..) + , characterName + , HasInventory(..) + , characterDamage + , characterHitpoints' + , characterHitpoints + , hitpointRecoveryRate + , speed + , body + + -- *** Body + , Body(..) + , initialBody + , knuckles + , Knuckles(..) + , fistDamageChance + , damageKnuckles + , fistfightingDamage + + -- * Character functions + , mkCharacter + , pickUpItem + , isDead + , isFullyHealed + , damage + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick +import Data.Aeson.Generic.DerivingVia +import Data.Aeson (ToJSON, FromJSON) +import Data.Coerce (coerce) +import Test.QuickCheck +import Test.QuickCheck.Instances.Vector () +import Test.QuickCheck.Arbitrary.Generic +import Test.QuickCheck.Gen (chooseUpTo) +import Test.QuickCheck.Checkers (EqProp) +import Control.Monad.State.Lazy (execState) +import Control.Monad.Trans.State.Lazy (execStateT) +-------------------------------------------------------------------------------- +import Xanthous.Util.QuickCheck +import Xanthous.Game.State +import Xanthous.Entities.Item +import Xanthous.Entities.Common +import Xanthous.Data + ( TicksPerTile, Hitpoints, Per, Ticks, (|*|), positioned ) +import qualified Xanthous.Entities.RawTypes as Raw +import Xanthous.Util (EqEqProp(EqEqProp), modifyKL) +import Xanthous.Monad (say_) +-------------------------------------------------------------------------------- + +-- | The status of the character's knuckles +-- +-- This struct is used to track the damage and then eventual build-up of +-- calluses when the character is fighting with their fists +data Knuckles = Knuckles + { -- | How damaged are the knuckles currently, from 0 to 5? + -- + -- At 0, no calluses will form + -- At 1 and up, the character will form calluses after a while + -- At 5, continuing to fistfight will deal the character even more damage + _knuckleDamage :: !Word + -- | How built-up are the character's calluses, from 0 to 5? + -- + -- Each level of calluses decreases the likelihood of being damaged when + -- fistfighting by 1%, up to 5 where the character will never be damaged + -- fistfighting + , _knuckleCalluses :: !Word + + -- | Number of turns that have passed since the last time the knuckles were + -- damaged + , _ticksSinceDamaged :: Ticks + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving EqProp via EqEqProp Knuckles + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Knuckles +makeLenses ''Knuckles + +instance Semigroup Knuckles where + (Knuckles dโ cโ tโ) <> (Knuckles dโ cโ tโ) = Knuckles + (min (dโ + dโ) 5) + (min (cโ + cโ) 5) + (max tโ tโ) + +instance Monoid Knuckles where + mempty = Knuckles 0 0 0 + +instance Arbitrary Knuckles where + arbitrary = do + _knuckleDamage <- fromIntegral <$> chooseUpTo 5 + _knuckleCalluses <- fromIntegral <$> chooseUpTo 5 + _ticksSinceDamaged <- arbitrary + pure Knuckles{..} + +-- | Likelihood that the character fighting with their fists will damage +-- themselves +fistDamageChance :: Knuckles -> Float +fistDamageChance knuckles + | calluses == 5 = 0 + | otherwise = baseChance - (0.01 * fromIntegral calluses) + where + baseChance = 0.08 + calluses = knuckles ^. knuckleCalluses + +-- | Damage the knuckles by a level (capping at the max knuckle damage) +damageKnuckles :: Knuckles -> Knuckles +damageKnuckles = execState $ do + knuckleDamage %= min 5 . succ + ticksSinceDamaged .= 0 + +-- | Damage taken when fistfighting and 'fistDamageChance' has occurred +fistfightingDamage :: Knuckles -> Hitpoints +fistfightingDamage knuckles + | knuckles ^. knuckleDamage == 5 = 2 + | otherwise = 1 + +stepKnuckles :: Ticks -> Knuckles -> AppM Knuckles +stepKnuckles ticks = execStateT . whenM (uses knuckleDamage (> 0)) $ do + ticksSinceDamaged += ticks + whenM (uses ticksSinceDamaged (>= 2000)) $ do + dam <- knuckleDamage <<.= 0 + knuckleCalluses %= min 5 . (+ dam) + ticksSinceDamaged .= 0 + lift $ say_ ["character", "body", "knuckles", "calluses"] + + +-- | Status of the character's body +data Body = Body + { _knuckles :: !Knuckles + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Body + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Body +makeLenses ''Body + +initialBody :: Body +initialBody = Body { _knuckles = mempty } + +-------------------------------------------------------------------------------- + +data Character = Character + { _inventory :: !Inventory + , _characterName :: !(Maybe Text) + , _characterHitpoints' :: !Double + , _speed :: !TicksPerTile + , _body :: !Body + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Character +makeFieldsNoPrefix ''Character + +characterHitpoints :: Character -> Hitpoints +characterHitpoints = views characterHitpoints' floor + +scrollOffset :: Int +scrollOffset = 5 + +instance Draw Character where + draw _ = visibleRegion rloc rreg $ str "@" + where + rloc = Location (negate scrollOffset, negate scrollOffset) + rreg = (2 * scrollOffset, 2 * scrollOffset) + drawPriority = const maxBound -- Character should always be on top, for now + +instance Brain Character where + step ticks = execStateT $ do + positioned . characterHitpoints' %= \hp -> + if hp > fromIntegral initialHitpoints + then hp + else hp + hitpointRecoveryRate |*| ticks + modifyKL (positioned . body . knuckles) $ lift . stepKnuckles ticks + +instance Entity Character where + description _ = "yourself" + entityChar _ = "@" + +instance Arbitrary Character where + arbitrary = genericArbitrary + +initialHitpoints :: Hitpoints +initialHitpoints = 10 + +hitpointRecoveryRate :: Double `Per` Ticks +hitpointRecoveryRate = 1.0 / (15 * coerce defaultSpeed) + +defaultSpeed :: TicksPerTile +defaultSpeed = 100 + +mkCharacter :: Character +mkCharacter = Character + { _inventory = mempty + , _characterName = Nothing + , _characterHitpoints' = fromIntegral initialHitpoints + , _speed = defaultSpeed + , _body = initialBody + } + +defaultCharacterDamage :: Hitpoints +defaultCharacterDamage = 1 + +-- | Returns the damage that the character currently does with an attack +-- TODO use double-handed/left-hand/right-hand here +characterDamage :: Character -> Hitpoints +characterDamage + = fromMaybe defaultCharacterDamage + . preview (inventory . wielded . wieldedItems . wieldableItem . Raw.damage) + +-- | Is the character fully healed up to or past their initial hitpoints? +isFullyHealed :: Character -> Bool +isFullyHealed = (>= initialHitpoints) . characterHitpoints + +-- | Is the character dead? +isDead :: Character -> Bool +isDead = (== 0) . characterHitpoints + +pickUpItem :: Item -> Character -> Character +pickUpItem it = inventory . backpack %~ (it <|) + +damage :: Hitpoints -> Character -> Character +damage (fromIntegral -> amount) = characterHitpoints' %~ \case + n | n <= amount -> 0 + | otherwise -> n - amount + +{-# ANN module ("Hlint: ignore Use newtype instead of data" :: String) #-} diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Common.hs b/users/grfn/xanthous/src/Xanthous/Entities/Common.hs new file mode 100644 index 000000000000..becd1b1ef62e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Common.hs @@ -0,0 +1,250 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +-- | +-- Module : Xanthous.Entities.Common +-- Description : Common data type definitions and utilities for entities +-- +-------------------------------------------------------------------------------- +module Xanthous.Entities.Common + ( -- * Inventory + Inventory(..) + , HasInventory(..) + , backpack + , wielded + , items + , InventoryPosition(..) + , describeInventoryPosition + , inventoryPosition + , itemsWithPosition + , removeItemFromPosition + + -- ** Wielded items + , Wielded(..) + , hands + , leftHand + , rightHand + , inLeftHand + , inRightHand + , doubleHanded + , wieldedItems + , WieldedItem(..) + , wieldedItem + , wieldableItem + , asWieldedItem + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson (ToJSON, FromJSON) +import Data.Aeson.Generic.DerivingVia +import Test.QuickCheck +import Test.QuickCheck.Checkers (EqProp) +-------------------------------------------------------------------------------- +import Xanthous.Data (Positioned(..), positioned) +import Xanthous.Util.QuickCheck +import Xanthous.Game.State +import Xanthous.Entities.Item +import Xanthous.Entities.RawTypes (WieldableItem, wieldable) +import Xanthous.Util (removeFirst, EqEqProp(..)) +-------------------------------------------------------------------------------- + +data WieldedItem = WieldedItem + { _wieldedItem :: Item + , _wieldableItem :: WieldableItem + -- ^ Invariant: item ^. itemType . wieldable โก Just wieldableItem + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + WieldedItem +makeFieldsNoPrefix ''WieldedItem + +asWieldedItem :: Prism' Item WieldedItem +asWieldedItem = prism' hither yon + where + yon item = WieldedItem item <$> item ^. itemType . wieldable + hither (WieldedItem item _) = item + +instance Brain WieldedItem where + step ticks (Positioned p wi) = + over positioned (\i -> WieldedItem i $ wi ^. wieldableItem) + <$> step ticks (Positioned p $ wi ^. wieldedItem) + +instance Draw WieldedItem where + draw = draw . view wieldedItem + +instance Entity WieldedItem where + entityAttributes = entityAttributes . view wieldedItem + description = description . view wieldedItem + entityChar = entityChar . view wieldedItem + +instance Arbitrary WieldedItem where + arbitrary = genericArbitrary <&> \wi -> + wi & wieldedItem . itemType . wieldable ?~ wi ^. wieldableItem + +data Wielded + = DoubleHanded WieldedItem + | Hands { _leftHand :: !(Maybe WieldedItem) + , _rightHand :: !(Maybe WieldedItem) + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Wielded + deriving (ToJSON, FromJSON) + via WithOptions '[ 'SumEnc 'ObjWithSingleField ] + Wielded + +nothingWielded :: Wielded +nothingWielded = Hands Nothing Nothing + +hands :: Prism' Wielded (Maybe WieldedItem, Maybe WieldedItem) +hands = prism' (uncurry Hands) $ \case + Hands l r -> Just (l, r) + _ -> Nothing + +leftHand :: Traversal' Wielded (Maybe WieldedItem) +leftHand = hands . _1 + +inLeftHand :: WieldedItem -> Wielded +inLeftHand wi = Hands (Just wi) Nothing + +rightHand :: Traversal' Wielded (Maybe WieldedItem) +rightHand = hands . _2 + +inRightHand :: WieldedItem -> Wielded +inRightHand wi = Hands Nothing (Just wi) + +doubleHanded :: Prism' Wielded WieldedItem +doubleHanded = prism' DoubleHanded $ \case + DoubleHanded i -> Just i + _ -> Nothing + +wieldedItems :: Traversal' Wielded WieldedItem +wieldedItems k (DoubleHanded wielded) = DoubleHanded <$> k wielded +wieldedItems k (Hands l r) = Hands <$> _Just k l <*> _Just k r + +data Inventory = Inventory + { _backpack :: Vector Item + , _wielded :: Wielded + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Inventory + deriving EqProp via EqEqProp Inventory + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Inventory +makeFieldsNoPrefix ''Inventory + +items :: Traversal' Inventory Item +items k (Inventory bp w) = Inventory + <$> traversed k bp + <*> (wieldedItems . wieldedItem) k w + +type instance Element Inventory = Item + +instance MonoFunctor Inventory where + omap = over items + +instance MonoFoldable Inventory where + ofoldMap = foldMapOf items + ofoldr = foldrOf items + ofoldl' = foldlOf' items + otoList = toListOf items + oall = allOf items + oany = anyOf items + onull = nullOf items + ofoldr1Ex = foldr1Of items + ofoldl1Ex' = foldl1Of' items + headEx = headEx . toListOf items + lastEx = lastEx . toListOf items + +instance MonoTraversable Inventory where + otraverse = traverseOf items + +instance Semigroup Inventory where + invโ <> invโ = + let backpack' = invโ ^. backpack <> invโ ^. backpack + (wielded', backpack'') = case (invโ ^. wielded, invโ ^. wielded) of + (wieldedโ, wieldedโ@(DoubleHanded _)) -> + (wieldedโ, backpack' <> fromList (wieldedโ ^.. wieldedItems . wieldedItem)) + (wieldedโ, wieldedโ@(Hands (Just _) (Just _))) -> + (wieldedโ, backpack' <> fromList (wieldedโ ^.. wieldedItems . wieldedItem)) + (wieldedโ, Hands Nothing Nothing) -> (wieldedโ, backpack') + (Hands Nothing Nothing, wieldedโ) -> (wieldedโ, backpack') + (Hands (Just lโ) Nothing, Hands Nothing (Just rโ)) -> + (Hands (Just lโ) (Just rโ), backpack') + (wieldedโ@(DoubleHanded _), wieldedโ) -> + (wieldedโ, backpack' <> fromList (wieldedโ ^.. wieldedItems . wieldedItem)) + (Hands Nothing (Just rโ), Hands Nothing (Just rโ)) -> + (Hands Nothing (Just rโ), rโ ^. wieldedItem <| backpack') + (Hands Nothing rโ, Hands (Just lโ) Nothing) -> + (Hands (Just lโ) rโ, backpack') + (Hands (Just lโ) Nothing, Hands (Just lโ) Nothing) -> + (Hands (Just lโ) Nothing, lโ ^. wieldedItem <| backpack') + (Hands (Just lโ) (Just rโ), Hands Nothing (Just rโ)) -> + (Hands (Just lโ) (Just rโ), rโ ^. wieldedItem <| backpack') + (Hands (Just lโ) (Just rโ), Hands (Just lโ) Nothing) -> + (Hands (Just lโ) (Just rโ), lโ ^. wieldedItem <| backpack') + in Inventory backpack'' wielded' + +instance Monoid Inventory where + mempty = Inventory mempty $ Hands Nothing Nothing + +class HasInventory s a | s -> a where + inventory :: Lens' s a + {-# MINIMAL inventory #-} + +-- | Representation for where in the inventory an item might be +data InventoryPosition + = Backpack + | LeftHand + | RightHand + | BothHands + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary InventoryPosition + +-- | Return a human-readable description of the given 'InventoryPosition' +describeInventoryPosition :: InventoryPosition -> Text +describeInventoryPosition Backpack = "In backpack" +describeInventoryPosition LeftHand = "Wielded, in left hand" +describeInventoryPosition RightHand = "Wielded, in right hand" +describeInventoryPosition BothHands = "Wielded, in both hands" + +-- | Given a position in the inventory, return a traversal on the inventory over +-- all the items in that position +inventoryPosition :: InventoryPosition -> Traversal' Inventory Item +inventoryPosition Backpack = backpack . traversed +inventoryPosition LeftHand = wielded . leftHand . _Just . wieldedItem +inventoryPosition RightHand = wielded . leftHand . _Just . wieldedItem +inventoryPosition BothHands = wielded . doubleHanded . wieldedItem + +-- | A fold over all the items in the inventory accompanied by their position in +-- the inventory +-- +-- Invariant: This will return items in the same order as 'items' +itemsWithPosition :: Fold Inventory (InventoryPosition, Item) +itemsWithPosition = folding $ (<>) <$> backpackItems <*> handItems + where + backpackItems = toListOf $ backpack . folded . to (Backpack ,) + handItems inv = case inv ^. wielded of + DoubleHanded i -> pure (BothHands, i ^. wieldedItem) + Hands l r -> (l ^.. folded . wieldedItem . to (LeftHand ,)) + <> (r ^.. folded . wieldedItem . to (RightHand ,)) + +-- | Remove the first item equal to 'Item' from the given position in the +-- inventory +removeItemFromPosition :: InventoryPosition -> Item -> Inventory -> Inventory +removeItemFromPosition Backpack item inv + = inv & backpack %~ removeFirst (== item) +removeItemFromPosition LeftHand item inv + = inv & wielded . leftHand %~ filter ((/= item) . view wieldedItem) +removeItemFromPosition RightHand item inv + = inv & wielded . rightHand %~ filter ((/= item) . view wieldedItem) +removeItemFromPosition BothHands item inv + | has (wielded . doubleHanded . wieldedItem . filtered (== item)) inv + = inv & wielded .~ nothingWielded + | otherwise + = inv diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Creature.hs b/users/grfn/xanthous/src/Xanthous/Entities/Creature.hs new file mode 100644 index 000000000000..3ea610795e98 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Creature.hs @@ -0,0 +1,88 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Creature + ( -- * Creature + Creature(..) + -- ** Lenses + , creatureType + , hitpoints + , hippocampus + , inventory + + -- ** Creature functions + , damage + , isDead + , visionRadius + + -- * Hippocampus + , Hippocampus(..) + -- ** Lenses + , destination + -- ** Destination + , Destination(..) + , destinationFromPos + -- *** Lenses + , destinationPosition + , destinationProgress + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Test.QuickCheck +import Data.Aeson.Generic.DerivingVia +import Data.Aeson (ToJSON, FromJSON) +-------------------------------------------------------------------------------- +import Xanthous.AI.Gormlak +import Xanthous.Entities.RawTypes hiding + (Creature, description, damage) +import qualified Xanthous.Entities.RawTypes as Raw +import Xanthous.Game.State +import Xanthous.Data +import Xanthous.Data.Entities +import Xanthous.Entities.Creature.Hippocampus +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +import Xanthous.Entities.Common (Inventory, HasInventory(..)) +-------------------------------------------------------------------------------- + +data Creature = Creature + { _creatureType :: !CreatureType + , _hitpoints :: !Hitpoints + , _hippocampus :: !Hippocampus + , _inventory :: !Inventory + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Draw via DrawRawCharPriority "_creatureType" 1000 Creature + deriving Arbitrary via GenericArbitrary Creature + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Creature +makeFieldsNoPrefix ''Creature + +instance HasVisionRadius Creature where + visionRadius = const 50 -- TODO + +instance Brain Creature where + step = brainVia GormlakBrain + entityCanMove = const True + +instance Entity Creature where + entityAttributes _ = defaultEntityAttributes + & blocksObject .~ True + description = view $ creatureType . Raw.description + entityChar = view $ creatureType . char + entityCollision = const $ Just Combat + +-------------------------------------------------------------------------------- + +damage :: Hitpoints -> Creature -> Creature +damage amount = hitpoints %~ \hp -> + if hp <= amount + then 0 + else hp - amount + +isDead :: Creature -> Bool +isDead = views hitpoints (== 0) + +{-# ANN module ("Hlint: ignore Use newtype instead of data" :: String) #-} diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Creature/Hippocampus.hs b/users/grfn/xanthous/src/Xanthous/Entities/Creature/Hippocampus.hs new file mode 100644 index 000000000000..9d5cc134517f --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Creature/Hippocampus.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Creature.Hippocampus + (-- * Hippocampus + Hippocampus(..) + , initialHippocampus + -- ** Lenses + , destination + , greetedCharacter + -- ** Destination + , Destination(..) + , destinationFromPos + -- *** Lenses + , destinationPosition + , destinationProgress + ) +where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson.Generic.DerivingVia +import Data.Aeson (ToJSON, FromJSON) +import Test.QuickCheck +import Test.QuickCheck.Arbitrary.Generic +-------------------------------------------------------------------------------- +import Xanthous.Data +import Xanthous.Util.QuickCheck +-------------------------------------------------------------------------------- + + +data Destination = Destination + { _destinationPosition :: !Position + -- | The progress towards the destination, tracked as an offset from the + -- creature's original position. + -- + -- When this value reaches >= 1, the creature has reached their destination + , _destinationProgress :: !Tiles + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Destination +instance Arbitrary Destination where arbitrary = genericArbitrary +makeLenses ''Destination + +destinationFromPos :: Position -> Destination +destinationFromPos _destinationPosition = + let _destinationProgress = 0 + in Destination{..} + +data Hippocampus = Hippocampus + { _destination :: !(Maybe Destination) + , -- | Has this creature greeted the character in any way yet? + -- + -- Some creature types ignore this field + _greetedCharacter :: !Bool + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Hippocampus + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Hippocampus +makeLenses ''Hippocampus + +initialHippocampus :: Hippocampus +initialHippocampus = Hippocampus + { _destination = Nothing + , _greetedCharacter = False + } diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Draw/Util.hs b/users/grfn/xanthous/src/Xanthous/Entities/Draw/Util.hs new file mode 100644 index 000000000000..aa6c5fa4fc47 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Draw/Util.hs @@ -0,0 +1,31 @@ +module Xanthous.Entities.Draw.Util where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick.Widgets.Border.Style +import Brick.Types (Edges(..)) +-------------------------------------------------------------------------------- + +borderFromEdges :: BorderStyle -> Edges Bool -> Char +borderFromEdges bstyle edges = ($ bstyle) $ case edges of + Edges False False False False -> const 'โ' + + Edges True False False False -> bsVertical + Edges False True False False -> bsVertical + Edges False False True False -> bsHorizontal + Edges False False False True -> bsHorizontal + + Edges True True False False -> bsVertical + Edges True False True False -> bsCornerBR + Edges True False False True -> bsCornerBL + + Edges False True True False -> bsCornerTR + Edges False True False True -> bsCornerTL + Edges False False True True -> bsHorizontal + + Edges False True True True -> bsIntersectT + Edges True False True True -> bsIntersectB + Edges True True False True -> bsIntersectL + Edges True True True False -> bsIntersectR + + Edges True True True True -> bsIntersectFull diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs b/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs new file mode 100644 index 000000000000..a0c037a1b4ed --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs @@ -0,0 +1,63 @@ +{-# LANGUAGE StandaloneDeriving #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Entities () where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Test.QuickCheck +import qualified Test.QuickCheck.Gen as Gen +import Data.Aeson +-------------------------------------------------------------------------------- +import Xanthous.Entities.Character +import Xanthous.Entities.Item +import Xanthous.Entities.Creature +import Xanthous.Entities.Environment +import Xanthous.Entities.Marker +import Xanthous.Game.State +import Xanthous.Util.QuickCheck +import Data.Aeson.Generic.DerivingVia +-------------------------------------------------------------------------------- + +instance Arbitrary SomeEntity where + arbitrary = Gen.oneof + [ SomeEntity <$> arbitrary @Character + , SomeEntity <$> arbitrary @Item + , SomeEntity <$> arbitrary @Creature + , SomeEntity <$> arbitrary @Wall + , SomeEntity <$> arbitrary @Door + , SomeEntity <$> arbitrary @GroundMessage + , SomeEntity <$> arbitrary @Staircase + , SomeEntity <$> arbitrary @Marker + ] + +instance FromJSON SomeEntity where + parseJSON = withObject "Entity" $ \obj -> do + (entityType :: Text) <- obj .: "type" + case entityType of + "Character" -> SomeEntity @Character <$> obj .: "data" + "Item" -> SomeEntity @Item <$> obj .: "data" + "Creature" -> SomeEntity @Creature <$> obj .: "data" + "Wall" -> SomeEntity @Wall <$> obj .: "data" + "Door" -> SomeEntity @Door <$> obj .: "data" + "GroundMessage" -> SomeEntity @GroundMessage <$> obj .: "data" + "Staircase" -> SomeEntity @Staircase <$> obj .: "data" + "Marker" -> SomeEntity @Marker <$> obj .: "data" + _ -> fail . unpack $ "Invalid entity type \"" <> entityType <> "\"" + +deriving via WithOptions '[ FieldLabelModifier '[Drop 1] ] GameLevel + instance FromJSON GameLevel +deriving via WithOptions '[ FieldLabelModifier '[Drop 1] ] GameState + instance FromJSON GameState + +instance Entity SomeEntity where + entityAttributes (SomeEntity ent) = entityAttributes ent + description (SomeEntity ent) = description ent + entityChar (SomeEntity ent) = entityChar ent + entityCollision (SomeEntity ent) = entityCollision ent + +instance Function SomeEntity where + function = functionJSON + +instance CoArbitrary SomeEntity where + coarbitrary = coarbitrary . encode diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs-boot b/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs-boot new file mode 100644 index 000000000000..519a862c6a5a --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Entities.hs-boot @@ -0,0 +1,14 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +module Xanthous.Entities.Entities where + +import Test.QuickCheck +import Data.Aeson +import Xanthous.Game.State (SomeEntity, GameState, Entity) + +instance Arbitrary SomeEntity +instance Function SomeEntity +instance CoArbitrary SomeEntity +instance FromJSON SomeEntity +instance Entity SomeEntity + +instance FromJSON GameState diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Environment.hs b/users/grfn/xanthous/src/Xanthous/Entities/Environment.hs new file mode 100644 index 000000000000..b45a91eabed2 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Environment.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE TemplateHaskell #-} +module Xanthous.Entities.Environment + ( + -- * Walls + Wall(..) + + -- * Doors + , Door(..) + , open + , closed + , locked + , unlockedDoor + + -- * Messages + , GroundMessage(..) + + -- * Stairs + , Staircase(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Test.QuickCheck +import Brick (str) +import Brick.Widgets.Border.Style (unicode) +import Brick.Types (Edges(..)) +import Data.Aeson +import Data.Aeson.Generic.DerivingVia +-------------------------------------------------------------------------------- +import Xanthous.Entities.Draw.Util +import Xanthous.Data +import Xanthous.Data.Entities +import Xanthous.Game.State +import Xanthous.Util.QuickCheck +-------------------------------------------------------------------------------- + +data Wall = Wall + deriving stock (Show, Eq, Ord, Generic, Enum) + deriving anyclass (NFData, CoArbitrary, Function) + +instance ToJSON Wall where + toJSON = const $ String "Wall" + +instance FromJSON Wall where + parseJSON = withText "Wall" $ \case + "Wall" -> pure Wall + _ -> fail "Invalid Wall: expected Wall" + +instance Brain Wall where step = brainVia Brainless + +instance Entity Wall where + entityAttributes _ = defaultEntityAttributes + & blocksVision .~ True + & blocksObject .~ True + description _ = "a wall" + entityChar _ = "โผ" + +instance Arbitrary Wall where + arbitrary = pure Wall + +wallEdges :: (MonoFoldable mono, Element mono ~ SomeEntity) + => Neighbors mono -> Edges Bool +wallEdges neighs = any (entityIs @Wall) <$> edges neighs + +instance Draw Wall where + drawWithNeighbors neighs _wall = + str . pure . borderFromEdges unicode $ wallEdges neighs + +data Door = Door + { _open :: Bool + , _locked :: Bool + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function, ToJSON, FromJSON) + deriving Arbitrary via GenericArbitrary Door +makeLenses ''Door + +instance Draw Door where + drawWithNeighbors neighs door + = str . pure . ($ door ^. open) $ case wallEdges neighs of + Edges True False False False -> vertDoor + Edges False True False False -> vertDoor + Edges True True False False -> vertDoor + Edges False False True False -> horizDoor + Edges False False False True -> horizDoor + Edges False False True True -> horizDoor + _ -> allsidesDoor + where + horizDoor True = 'โฃ' + horizDoor False = 'แ' + vertDoor True = '[' + vertDoor False = 'ว' + allsidesDoor True = '+' + allsidesDoor False = 'โฅ' + +instance Brain Door where step = brainVia Brainless + +instance Entity Door where + entityAttributes door = defaultEntityAttributes + & blocksVision .~ not (door ^. open) + description door | door ^. open = "an open door" + | otherwise = "a closed door" + entityChar _ = "d" + entityCollision door | door ^. open = Nothing + | otherwise = Just Stop + +closed :: Lens' Door Bool +closed = open . involuted not + +-- | A closed, unlocked door +unlockedDoor :: Door +unlockedDoor = Door + { _open = False + , _locked = False + } + +-------------------------------------------------------------------------------- + +newtype GroundMessage = GroundMessage Text + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary GroundMessage + deriving (ToJSON, FromJSON) + via WithOptions '[ 'TagSingleConstructors 'True + , 'SumEnc 'ObjWithSingleField + ] + GroundMessage + deriving Draw + via DrawStyledCharacter ('Just 'Yellow) 'Nothing "โ" + GroundMessage +instance Brain GroundMessage where step = brainVia Brainless + +instance Entity GroundMessage where + description = const "a message on the ground. Press r. to read it." + entityChar = const "โ" + entityCollision = const Nothing + +-------------------------------------------------------------------------------- + +data Staircase = UpStaircase | DownStaircase + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Staircase + deriving (ToJSON, FromJSON) + via WithOptions '[ 'TagSingleConstructors 'True + , 'SumEnc 'ObjWithSingleField + ] + Staircase +instance Brain Staircase where step = brainVia Brainless + +instance Draw Staircase where + draw UpStaircase = str "<" + draw DownStaircase = str ">" + +instance Entity Staircase where + description UpStaircase = "a staircase leading upwards" + description DownStaircase = "a staircase leading downwards" + entityChar UpStaircase = "<" + entityChar DownStaircase = ">" + entityCollision = const Nothing diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Item.hs b/users/grfn/xanthous/src/Xanthous/Entities/Item.hs new file mode 100644 index 000000000000..eadd62569663 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Item.hs @@ -0,0 +1,76 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Item + ( Item(..) + , itemType + , density + , volume + , newWithType + , isEdible + , weight + , fullDescription + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Test.QuickCheck (Arbitrary, CoArbitrary, Function) +import Data.Aeson (ToJSON, FromJSON) +import Data.Aeson.Generic.DerivingVia +import Control.Monad.Random (MonadRandom) +-------------------------------------------------------------------------------- +import Xanthous.Entities.RawTypes (ItemType) +import qualified Xanthous.Entities.RawTypes as Raw +import Xanthous.Game.State +import Xanthous.Data (Grams, Per, Cubic, Meters, (|*|)) +import Xanthous.Util.QuickCheck (GenericArbitrary(GenericArbitrary)) +import Xanthous.Random (choose, FiniteInterval(..)) +-------------------------------------------------------------------------------- + +data Item = Item + { _itemType :: ItemType + , _density :: Grams `Per` Cubic Meters + , _volume :: Cubic Meters + } + deriving stock (Eq, Show, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Draw via DrawRawChar "_itemType" Item + deriving Arbitrary via GenericArbitrary Item + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + Item +makeLenses ''Item + +-- deriving via (Brainless Item) instance Brain Item +instance Brain Item where step = brainVia Brainless + +instance Entity Item where + description = view $ itemType . Raw.description + entityChar = view $ itemType . Raw.char + entityCollision = const Nothing + +newWithType :: MonadRandom m => ItemType -> m Item +newWithType _itemType = do + _density <- choose . FiniteInterval $ _itemType ^. Raw.density + _volume <- choose . FiniteInterval $ _itemType ^. Raw.volume + pure Item {..} + +isEdible :: Item -> Bool +isEdible = Raw.isEdible . view itemType + +-- | The weight of this item, calculated by multiplying its volume by the +-- density of its material +weight :: Item -> Grams +weight item = (item ^. density) |*| (item ^. volume) + +-- | Describe the item in full detail +fullDescription :: Item -> Text +fullDescription item = unlines + [ item ^. itemType . Raw.description + , "" + , item ^. itemType . Raw.longDescription + , "" + , "volume: " <> tshow (item ^. volume) + , "density: " <> tshow (item ^. density) + , "weight: " <> tshow (weight item) + ] diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Marker.hs b/users/grfn/xanthous/src/Xanthous/Entities/Marker.hs new file mode 100644 index 000000000000..14d02872ed4e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Marker.hs @@ -0,0 +1,41 @@ +-------------------------------------------------------------------------------- +module Xanthous.Entities.Marker ( Marker(..) ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson +import Test.QuickCheck +import qualified Graphics.Vty.Attributes as Vty +import qualified Graphics.Vty.Image as Vty +import Brick.Widgets.Core (raw) +-------------------------------------------------------------------------------- +import Xanthous.Game.State +import Xanthous.Data.Entities (EntityAttributes(..)) +-------------------------------------------------------------------------------- + +-- | Mark on the map - for use in debugging / development only. +newtype Marker = Marker Text + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (Semigroup, Monoid, ToJSON, FromJSON, Arbitrary) via Text + +instance Brain Marker where step = brainVia Brainless + +instance Entity Marker where + entityAttributes = const EntityAttributes + { _blocksVision = False + , _blocksObject = False + , _collision = Stop + } + description (Marker m) = "[M] " <> m + entityChar = const $ "X" & style .~ markerStyle + entityCollision = const Nothing + +instance Draw Marker where + draw = const . raw $ Vty.char markerStyle 'X' + drawPriority = const maxBound + +markerStyle :: Vty.Attr +markerStyle = Vty.defAttr + `Vty.withForeColor` Vty.red + `Vty.withBackColor` Vty.black diff --git a/users/grfn/xanthous/src/Xanthous/Entities/RawTypes.hs b/users/grfn/xanthous/src/Xanthous/Entities/RawTypes.hs new file mode 100644 index 000000000000..9f5cabecdca7 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/RawTypes.hs @@ -0,0 +1,277 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DuplicateRecordFields #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.RawTypes + ( + EntityRaw(..) + , _Creature + , _Item + + -- * Creatures + , CreatureType(..) + , hostile + -- ** Generation parameters + , CreatureGenerateParams(..) + , canGenerate + -- ** Language + , LanguageName(..) + , getLanguage + -- ** Attacks + , Attack(..) + + -- * Items + , ItemType(..) + -- ** Item sub-types + -- *** Edible + , EdibleItem(..) + , isEdible + -- *** Wieldable + , WieldableItem(..) + , isWieldable + + -- * Lens classes + , HasAttackMessage(..) + , HasAttacks(..) + , HasChance(..) + , HasChar(..) + , HasCreatureAttackMessage(..) + , HasDamage(..) + , HasDensity(..) + , HasDescription(..) + , HasEatMessage(..) + , HasEdible(..) + , HasEntityName(..) + , HasEquippedItem(..) + , HasFriendly(..) + , HasGenerateParams(..) + , HasHitpointsHealed(..) + , HasLanguage(..) + , HasLevelRange(..) + , HasLongDescription(..) + , HasMaxHitpoints(..) + , HasName(..) + , HasSayVerb(..) + , HasSpeed(..) + , HasVolume(..) + , HasWieldable(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Test.QuickCheck +import Data.Aeson.Generic.DerivingVia +import Data.Aeson (ToJSON, FromJSON) +import Data.Interval (Interval, lowerBound', upperBound') +import qualified Data.Interval as Interval +-------------------------------------------------------------------------------- +import Xanthous.Messages (Message(..)) +import Xanthous.Data (TicksPerTile, Hitpoints, Per, Grams, Cubic, Meters) +import Xanthous.Data.EntityChar +import Xanthous.Util.QuickCheck +import Xanthous.Generators.Speech (Language, gormlak, english) +import Xanthous.Orphans () +import Xanthous.Util (EqProp, EqEqProp(..)) +-------------------------------------------------------------------------------- + +-- | Identifiers for languages that creatures can speak. +-- +-- Non-verbal or non-sentient creatures have Nothing as their language +-- +-- At some point, we will likely want to make languages be defined in data files +-- somewhere, and reference them that way instead. +data LanguageName = Gormlak | English + deriving stock (Show, Eq, Ord, Generic, Enum, Bounded) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary LanguageName + deriving (ToJSON, FromJSON) + via WithOptions '[ AllNullaryToStringTag 'True ] + LanguageName + +-- | Resolve a 'LanguageName' into an actual 'Language' +getLanguage :: LanguageName -> Language +getLanguage Gormlak = gormlak +getLanguage English = english + +-- | Natural attacks for creature types +data Attack = Attack + { -- | the @{{creature}}@ @{{description}}@ + _description :: !Message + -- | Damage dealt + , _damage :: !Hitpoints + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Attack + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] + , OmitNothingFields 'True + ] + Attack +makeFieldsNoPrefix ''Attack + +-- | Description for generating an item equipped to a creature +data CreatureEquippedItem = CreatureEquippedItem + { -- | Name of the entity type to generate + _entityName :: !Text + -- | Chance of generating the item when generating the creature + -- + -- A chance of 1.0 will always generate the item + , _chance :: !Double + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary CreatureEquippedItem + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] + , OmitNothingFields 'True + ] + CreatureEquippedItem +makeFieldsNoPrefix ''CreatureEquippedItem + + +data CreatureGenerateParams = CreatureGenerateParams + { -- | Range of dungeon levels at which to generate this creature + _levelRange :: !(Interval Word) + -- | Item equipped to the creature + , _equippedItem :: !(Maybe CreatureEquippedItem) + } + deriving stock (Eq, Show, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary CreatureGenerateParams + deriving EqProp via EqEqProp CreatureGenerateParams + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + CreatureGenerateParams +makeFieldsNoPrefix ''CreatureGenerateParams + +instance Ord CreatureGenerateParams where + compare + = (compare `on` lowerBound' . _levelRange) + <> (compare `on` upperBound' . _levelRange) + <> (compare `on` _equippedItem) + +-- | Can a creature with these generate params be generated on this level? +canGenerate + :: Word -- ^ Level number + -> CreatureGenerateParams + -> Bool +canGenerate levelNumber gps = Interval.member levelNumber $ gps ^. levelRange + +data CreatureType = CreatureType + { _name :: !Text + , _description :: !Text + , _char :: !EntityChar + , _maxHitpoints :: !Hitpoints + , _friendly :: !Bool + , _speed :: !TicksPerTile + , _language :: !(Maybe LanguageName) + , -- | The verb, in present tense, for when the creature says something + _sayVerb :: !(Maybe Text) + , -- | The creature's natural attacks + _attacks :: !(NonNull (Vector Attack)) + -- | Parameters for generating the creature in levels + , _generateParams :: !(Maybe CreatureGenerateParams) + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary CreatureType + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] + , OmitNothingFields 'True + ] + CreatureType +makeFieldsNoPrefix ''CreatureType + +hostile :: Lens' CreatureType Bool +hostile = friendly . involuted not + +-------------------------------------------------------------------------------- + +data EdibleItem = EdibleItem + { _hitpointsHealed :: !Int + , _eatMessage :: !(Maybe Message) + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary EdibleItem + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + EdibleItem +makeFieldsNoPrefix ''EdibleItem + +data WieldableItem = WieldableItem + { _damage :: !Hitpoints + , _attackMessage :: !(Maybe Message) + -- | Message to use when a creature is using this item to attack the + -- character. + -- + -- Grammatically, should be of the form "The creature slashes you with its + -- dagger". + -- + -- = Parameters + -- + -- [@creature@ (type: 'Creature')] The creature doing the attacking + -- [@item@ (type: 'Item')] The item itself + , _creatureAttackMessage :: !(Maybe Message) + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary WieldableItem + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + WieldableItem +makeFieldsNoPrefix ''WieldableItem + +-------------------------------------------------------------------------------- + +data ItemType = ItemType + { _name :: !Text + , _description :: !Text + , _longDescription :: !Text + , _char :: !EntityChar + , _density :: !(Interval (Grams `Per` Cubic Meters)) + , _volume :: !(Interval (Cubic Meters)) + , _edible :: !(Maybe EdibleItem) + , _wieldable :: !(Maybe WieldableItem) + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary ItemType + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + ItemType +makeFieldsNoPrefix ''ItemType + +instance Ord ItemType where + compare x y + = compareOf name x y + <> compareOf description x y + <> compareOf longDescription x y + <> compareOf char x y + <> compareOf (density . to extractInterval) x y + <> compareOf (volume . to extractInterval) x y + <> compareOf edible x y + <> compareOf wieldable x y + where + compareOf l = comparing (view l) + extractInterval = lowerBound' &&& upperBound' + +-- | Can this item be eaten? +isEdible :: ItemType -> Bool +isEdible = has $ edible . _Just + +-- | Can this item be used as a weapon? +isWieldable :: ItemType -> Bool +isWieldable = has $ wieldable . _Just + +-------------------------------------------------------------------------------- + +data EntityRaw + = Creature !CreatureType + | Item !ItemType + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) + deriving Arbitrary via GenericArbitrary EntityRaw + deriving (FromJSON) + via WithOptions '[ SumEnc ObjWithSingleField ] + EntityRaw +makePrisms ''EntityRaw diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws.hs b/users/grfn/xanthous/src/Xanthous/Entities/Raws.hs new file mode 100644 index 000000000000..10f0d831934e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.Raws + ( raws + , raw + , RawType(..) + , rawsWithType + ) where +-------------------------------------------------------------------------------- +import Data.FileEmbed +import qualified Data.Yaml as Yaml +import Xanthous.Prelude +import System.FilePath.Posix +-------------------------------------------------------------------------------- +import Xanthous.Entities.RawTypes +import Xanthous.AI.Gormlak () +-------------------------------------------------------------------------------- +rawRaws :: [(FilePath, ByteString)] +rawRaws = $(embedDir "src/Xanthous/Entities/Raws") + +raws :: HashMap Text EntityRaw +raws + = mapFromList + . map (bimap + (pack . takeBaseName) + (either (error . Yaml.prettyPrintParseException) id + . Yaml.decodeEither')) + $ rawRaws + +raw :: Text -> Maybe EntityRaw +raw n = raws ^. at n + +class RawType (a :: Type) where + _RawType :: Prism' EntityRaw a + +instance RawType CreatureType where + _RawType = prism' Creature $ \case + Creature c -> Just c + _ -> Nothing + +instance RawType ItemType where + _RawType = prism' Item $ \case + Item i -> Just i + _ -> Nothing + +rawsWithType :: forall a. RawType a => HashMap Text a +rawsWithType = mapFromList . itoListOf (ifolded . _RawType) $ raws + +-------------------------------------------------------------------------------- diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/broken-dagger.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/broken-dagger.yaml new file mode 100644 index 000000000000..2d30e6986b6e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/broken-dagger.yaml @@ -0,0 +1,24 @@ +Item: + name: broken dagger + description: a short, broken dagger + longDescription: A short dagger with a twisted, chipped blade + char: + char: โ + style: + foreground: black + wieldable: + damage: 3 + attackMessage: + - You slash at the {{creature.creatureType.name}} with your dagger. + - You stab the {{creature.creatureType.name}} with your dagger. + creatureAttackMessage: + - The {{creature.creatureType.name}} slashes at you with its dagger. + - The {{creature.creatureType.name}} stabs you with its dagger. + # Just the steel, not the handle, for now + density: [7750 , 8050000] + # 15cm โ 45cm + # ร + # 2cm โ 3cm + # ร + # .5cm โ 1cm + volume: [0.15, 1.35] diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/gormlak.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/gormlak.yaml new file mode 100644 index 000000000000..ad3d9cb147da --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/gormlak.yaml @@ -0,0 +1,20 @@ +Creature: + name: gormlak + description: a gormlak + longDescription: | + A chittering imp-like creature with bright yellow horns and sharp claws. It + adores shiny objects and gathers in swarms. + char: + char: g + style: + foreground: red + maxHitpoints: 5 + speed: 125 + friendly: false + language: Gormlak + sayVerb: yells + attacks: + - description: + - claws you + - slashes you with its claws + damage: 1 diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/husk.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/husk.yaml new file mode 100644 index 000000000000..cdfcde616d21 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/husk.yaml @@ -0,0 +1,26 @@ +Creature: + name: husk + description: an empty husk of some humanoid creature + longDescription: | + An empty husk of a humanoid creature. All semblance of sentience has long + left its eyes; instead it shambles about aimlessly, always hungering for the + warmth of life. + char: + char: h + style: + foreground: black + maxHitpoints: 6 + speed: 110 + friendly: false + attacks: + - description: + - swings its arms at you + - elbows you + damage: 1 + - description: kicks you + damage: 2 + generateParams: + levelRange: [1, PosInf] + equippedItem: + entityName: broken-dagger + chance: 0.9 diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/noodles.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/noodles.yaml new file mode 100644 index 000000000000..c0501a18a8e0 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/noodles.yaml @@ -0,0 +1,14 @@ +Item: + name: noodles + description: "a big bowl o' noodles" + longDescription: You know exactly what kind of noodles + char: + char: 'n' + style: + foreground: yellow + edible: + hitpointsHealed: 2 + eatMessage: + - You slurp up the noodles. Yumm! + density: 500000 + volume: 0.001 diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/ooze.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/ooze.yaml new file mode 100644 index 000000000000..fe427c94abf7 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/ooze.yaml @@ -0,0 +1,15 @@ +Creature: + name: ooze + description: an ooze + longDescription: | + A jiggling, amorphous, bright green caustic blob + char: + char: o + style: + foreground: green + maxHitpoints: 3 + speed: 100 + friendly: false + attacks: + - description: slams into you + damage: 1 diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/rock.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/rock.yaml new file mode 100644 index 000000000000..e7492bf5fb6f --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/rock.yaml @@ -0,0 +1,10 @@ +Item: + name: rock + description: a rock + longDescription: a medium-sized rock made out of some unknown stone + char: . + wieldable: + damage: 1 + attackMessage: you hit the {{creature.creatureType.name}} in the head with your rock. + density: [ 1500000, 2500000 ] + volume: [ 0.000125, 0.001 ] diff --git a/users/grfn/xanthous/src/Xanthous/Entities/Raws/stick.yaml b/users/grfn/xanthous/src/Xanthous/Entities/Raws/stick.yaml new file mode 100644 index 000000000000..a7eae9c36666 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Entities/Raws/stick.yaml @@ -0,0 +1,22 @@ +Item: + name: stick + description: a wooden stick + longDescription: A sturdy branch broken off from some sort of tree + char: + char: โค + style: + foreground: yellow + wieldable: + damage: 2 + attackMessage: + - You bonk the {{creature.creatureType.name}} over the head with your stick. + - You bash the {{creature.creatureType.name}} on the noggin with your stick. + - You whack the {{creature.creatureType.name}} with your stick. + creatureAttackMessage: + - The {{creature.creatureType.name}} bonks you over the head with its stick. + - The {{creature.creatureType.name}} bashes you on the noggin with its stick. + - The {{creature.creatureType.name}} whacks you with its stick. + # https://www.sciencedirect.com/topics/agricultural-and-biological-sciences/wood-density + # it's a hard stick. so it's dense wood. + density: 890000 # g/mยณ + volume: [ 0.003, 0.006 ] # โ3.5 cm radius ร โ1m length diff --git a/users/grfn/xanthous/src/Xanthous/Game.hs b/users/grfn/xanthous/src/Xanthous/Game.hs new file mode 100644 index 000000000000..89c23f0de850 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game.hs @@ -0,0 +1,73 @@ +module Xanthous.Game + ( GameState(..) + , levels + , entities + , revealedPositions + , messageHistory + , randomGen + , promptState + , GamePromptState(..) + + , getInitialState + , initialStateFromSeed + + , positionedCharacter + , character + , characterPosition + , updateCharacterVision + , characterVisiblePositions + , entitiesAtCharacter + , revealedEntitiesAtPosition + + -- * Messages + , MessageHistory(..) + , HasMessages(..) + , HasTurn(..) + , HasDisplayedTurn(..) + , pushMessage + , previousMessage + , nextTurn + + -- * Collisions + , Collision(..) + , collisionAt + + -- * App monad + , AppT(..) + + -- * Saving the game + , saveGame + , loadGame + , saved + + -- * Debug State + , DebugState(..) + , debugState + , allRevealed + ) where +-------------------------------------------------------------------------------- +import qualified Codec.Compression.Zlib as Zlib +import Codec.Compression.Zlib.Internal (DecompressError) +import qualified Data.Aeson as JSON +import System.IO.Unsafe +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Xanthous.Game.State +import Xanthous.Game.Lenses +import Xanthous.Game.Arbitrary () +import Xanthous.Entities.Entities () +-------------------------------------------------------------------------------- + +saveGame :: GameState -> LByteString +saveGame = Zlib.compress . JSON.encode + +loadGame :: LByteString -> Maybe GameState +loadGame = JSON.decode <=< decompressZlibMay + where + decompressZlibMay bs + = unsafeDupablePerformIO + $ (let r = Zlib.decompress bs in r `seq` pure (Just r)) + `catch` \(_ :: DecompressError) -> pure Nothing + +saved :: Prism' LByteString GameState +saved = prism' saveGame loadGame diff --git a/users/grfn/xanthous/src/Xanthous/Game/Arbitrary.hs b/users/grfn/xanthous/src/Xanthous/Game/Arbitrary.hs new file mode 100644 index 000000000000..679bfe54597f --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Arbitrary.hs @@ -0,0 +1,53 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Game.Arbitrary where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (foldMap) +-------------------------------------------------------------------------------- +import Test.QuickCheck +import System.Random +import Data.Foldable (foldMap) +-------------------------------------------------------------------------------- +import Xanthous.Data.Levels +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Entities.Entities () +import Xanthous.Entities.Character +import Xanthous.Game.State +import Xanthous.Orphans () +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +-------------------------------------------------------------------------------- + +deriving via GenericArbitrary GameLevel instance Arbitrary GameLevel + +instance Arbitrary GameState where + arbitrary = do + chr <- arbitrary @Character + _upStaircasePosition <- arbitrary + _messageHistory <- arbitrary + levs <- arbitrary @(Levels GameLevel) + _levelRevealedPositions <- + fmap setFromList + . sublistOf + . foldMap (EntityMap.positions . _levelEntities) + $ levs + let (_characterEntityID, _levelEntities) = + EntityMap.insertAtReturningID _upStaircasePosition (SomeEntity chr) + $ levs ^. current . levelEntities + _levels = levs & current .~ GameLevel {..} + _randomGen <- mkStdGen <$> arbitrary + let _promptState = NoPrompt -- TODO + _activePanel <- arbitrary + _debugState <- arbitrary + let _autocommand = NoAutocommand + _memo <- arbitrary + _savefile <- arbitrary + pure $ GameState {..} + + +instance CoArbitrary GameLevel +instance Function GameLevel +instance CoArbitrary GameState +instance Function GameState diff --git a/users/grfn/xanthous/src/Xanthous/Game/Draw.hs b/users/grfn/xanthous/src/Xanthous/Game/Draw.hs new file mode 100644 index 000000000000..53ea1c96f8af --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Draw.hs @@ -0,0 +1,151 @@ +-------------------------------------------------------------------------------- +module Xanthous.Game.Draw + ( drawGame + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick hiding (loc, on) +import Brick.Widgets.Border +import Brick.Widgets.Border.Style +import Brick.Widgets.Edit +import Control.Monad.State.Lazy (evalState) +import Control.Monad.State.Class ( get, MonadState, gets ) +-------------------------------------------------------------------------------- +import Xanthous.Data +import Xanthous.Data.App (ResourceName, Panel(..)) +import qualified Xanthous.Data.App as Resource +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Game.State +import Xanthous.Entities.Common (Wielded(..), wielded, backpack) +import Xanthous.Entities.Character +import Xanthous.Entities.Item (Item) +import Xanthous.Game + ( characterPosition + , character + , revealedEntitiesAtPosition + ) +import Xanthous.Game.Prompt +import Xanthous.Orphans () +-------------------------------------------------------------------------------- + +cursorPosition :: GameState -> Widget ResourceName -> Widget ResourceName +cursorPosition game + | WaitingPrompt _ (Prompt _ _ (preview promptStatePosition -> Just pos) _ _) + <- game ^. promptState + = showCursor Resource.Prompt (pos ^. loc) + | otherwise + = showCursor Resource.Character (game ^. characterPosition . loc) + +drawMessages :: MessageHistory -> Widget ResourceName +drawMessages = txtWrap . (<> " ") . unwords . reverse . oextract + +drawPromptState :: GamePromptState m -> Widget ResourceName +drawPromptState NoPrompt = emptyWidget +drawPromptState (WaitingPrompt msg (Prompt _ pt ps pri _)) = + case (pt, ps, pri) of + (SStringPrompt, StringPromptState edit, mDef) -> + txt msg + <+> txt (maybe "" (\def -> "(default: " <> def <> ") ") mDef) + <+> renderEditor (txt . fold) True edit + (SDirectionPrompt, DirectionPromptState, _) -> txtWrap msg + (SMenu, _, menuItems) -> + txtWrap msg + <=> foldl' (<=>) emptyWidget (map drawMenuItem $ itoList menuItems) + _ -> txtWrap msg + where + drawMenuItem (chr, MenuOption m _) = + str ("[" <> pure chr <> "] ") <+> txtWrap m + +drawEntities + :: forall m. MonadState GameState m + => m (Widget ResourceName) +drawEntities = do + allEnts <- use entities + let entityPositions = EntityMap.positions allEnts + maxY = fromMaybe 0 $ maximumOf (folded . y) entityPositions + maxX = fromMaybe 0 $ maximumOf (folded . x) entityPositions + rows = traverse mkRow [0..maxY] + mkRow rowY = hBox <$> traverse (renderEntityAt . flip Position rowY) [0..maxX] + renderEntityAt pos + = renderTopEntity pos <$> revealedEntitiesAtPosition pos + renderTopEntity pos ents + = let neighbors = EntityMap.neighbors pos allEnts + in maybe (str " ") (drawWithNeighbors neighbors) + $ maximumBy (compare `on` drawPriority) + <$> fromNullable ents + vBox <$> rows + +drawMap :: MonadState GameState m => m (Widget ResourceName) +drawMap = do + cursorPos <- gets cursorPosition + viewport Resource.MapViewport Both . cursorPos <$> drawEntities + +bullet :: Char +bullet = 'โข' + +drawInventoryPanel :: GameState -> Widget ResourceName +drawInventoryPanel game + = drawWielded (game ^. character . inventory . wielded) + <=> drawBackpack (game ^. character . inventory . backpack) + where + drawWielded (Hands Nothing Nothing) = emptyWidget + drawWielded (DoubleHanded i) = + txtWrap $ "You are holding " <> description i <> " in both hands" + drawWielded (Hands l r) = drawHand "left" l <=> drawHand "right" r + drawHand side = maybe emptyWidget $ \i -> + txtWrap ( "You are holding " + <> description i + <> " in your " <> side <> " hand" + ) + <=> txt " " + + drawBackpack :: Vector Item -> Widget ResourceName + drawBackpack Empty = txtWrap "Your backpack is empty right now." + drawBackpack backpackItems + = txtWrap ( "You are currently carrying the following items in your " + <> "backpack:") + <=> txt " " + <=> foldl' (<=>) emptyWidget + (map + (txtWrap . ((bullet <| " ") <>) . description) + backpackItems) + + +drawPanel :: GameState -> Panel -> Widget ResourceName +drawPanel game panel + = border + . hLimit 35 + . viewport (Resource.Panel panel) Vertical + . case panel of + InventoryPanel -> drawInventoryPanel + ItemDescriptionPanel desc -> const $ txtWrap desc + $ game + +drawCharacterInfo :: Character -> Widget ResourceName +drawCharacterInfo ch = txt " " <+> charName <+> charHitpoints + where + charName | Just n <- ch ^. characterName + = txt $ n <> " " + | otherwise + = emptyWidget + charHitpoints + = txt "Hitpoints: " + <+> txt (tshow $ let Hitpoints hp = characterHitpoints ch in hp) + +drawGame :: GameState -> [Widget ResourceName] +drawGame = evalState $ do + game <- get + drawnMap <- drawMap + pure + . pure + . withBorderStyle unicode + $ case game ^. promptState of + NoPrompt -> drawMessages (game ^. messageHistory) + _ -> emptyWidget + <=> drawPromptState (game ^. promptState) + <=> + (maybe emptyWidget (drawPanel game) (game ^. activePanel) + <+> border drawnMap + ) + <=> drawCharacterInfo (game ^. character) diff --git a/users/grfn/xanthous/src/Xanthous/Game/Env.hs b/users/grfn/xanthous/src/Xanthous/Game/Env.hs new file mode 100644 index 000000000000..5d7b275c8a0b --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Env.hs @@ -0,0 +1,37 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Game.Env + ( Config(..) + , defaultConfig + , disableSaving + , GameEnv(..) + , eventChan + , config + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick.BChan (BChan) +import Xanthous.Data.App (AppEvent) +-------------------------------------------------------------------------------- + +data Config = Config + { _disableSaving :: Bool + } + deriving stock (Generic, Show, Eq) +makeLenses ''Config +{-# ANN Config ("HLint: ignore Use newtype instead of data" :: String) #-} + +defaultConfig :: Config +defaultConfig = Config + { _disableSaving = False + } + +-------------------------------------------------------------------------------- + +data GameEnv = GameEnv + { _eventChan :: BChan AppEvent + , _config :: Config + } + deriving stock (Generic) +makeLenses ''GameEnv diff --git a/users/grfn/xanthous/src/Xanthous/Game/Lenses.hs b/users/grfn/xanthous/src/Xanthous/Game/Lenses.hs new file mode 100644 index 000000000000..c692a3b47944 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Lenses.hs @@ -0,0 +1,178 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +-------------------------------------------------------------------------------- +module Xanthous.Game.Lenses + ( clearMemo + , positionedCharacter + , character + , characterPosition + , updateCharacterVision + , characterVisiblePositions + , characterVisibleEntities + , positionIsCharacterVisible + , getInitialState + , initialStateFromSeed + , entitiesAtCharacter + , revealedEntitiesAtPosition + , hearingRadius + + -- * Collisions + , Collision(..) + , entitiesCollision + , collisionAt + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import System.Random +import Control.Monad.State +import Control.Monad.Random (getRandom) +-------------------------------------------------------------------------------- +import Xanthous.Game.State +import qualified Xanthous.Game.Memo as Memo +import Xanthous.Data +import Xanthous.Data.Levels +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Data.EntityMap.Graphics + (visiblePositions, visibleEntities) +import Xanthous.Data.VectorBag +import Xanthous.Entities.Character (Character, mkCharacter) +import {-# SOURCE #-} Xanthous.Entities.Entities () +import Xanthous.Game.Memo (emptyMemoState, MemoState) +import Xanthous.Data.Memo (fillWithM, Memoized) +-------------------------------------------------------------------------------- + +getInitialState :: IO GameState +getInitialState = initialStateFromSeed <$> getRandom + +initialStateFromSeed :: Int -> GameState +initialStateFromSeed seed = + let _randomGen = mkStdGen seed + chr = mkCharacter + _upStaircasePosition = Position 0 0 + (_characterEntityID, _levelEntities) + = EntityMap.insertAtReturningID + _upStaircasePosition + (SomeEntity chr) + mempty + _levelRevealedPositions = mempty + level = GameLevel {..} + _levels = oneLevel level + _messageHistory = mempty + _promptState = NoPrompt + _activePanel = Nothing + _debugState = DebugState + { _allRevealed = False + } + _savefile = Nothing + _autocommand = NoAutocommand + _memo = emptyMemoState + in GameState {..} + +clearMemo :: MonadState GameState m => Lens' MemoState (Memoized k v) -> m () +clearMemo l = memo %= Memo.clear l + +positionedCharacter :: Lens' GameState (Positioned Character) +positionedCharacter = lens getPositionedCharacter setPositionedCharacter + where + setPositionedCharacter :: GameState -> Positioned Character -> GameState + setPositionedCharacter game chr + = game + & entities . at (game ^. characterEntityID) + ?~ fmap SomeEntity chr + + getPositionedCharacter :: GameState -> Positioned Character + getPositionedCharacter game + = over positioned + ( fromMaybe (error "Invariant error: Character was not a character!") + . downcastEntity + ) + . fromMaybe (error "Invariant error: Character not found!") + $ EntityMap.lookupWithPosition + (game ^. characterEntityID) + (game ^. entities) + + +character :: Lens' GameState Character +character = positionedCharacter . positioned + +characterPosition :: Lens' GameState Position +characterPosition = positionedCharacter . position + +-- TODO make this dynamic +visionRadius :: Word +visionRadius = 12 + +-- TODO make this dynamic +hearingRadius :: Word +hearingRadius = 12 + +-- | Update the revealed entities at the character's position based on their +-- vision +updateCharacterVision :: GameState -> GameState +updateCharacterVision = execState $ do + positions <- characterVisiblePositions + revealedPositions <>= positions + +characterVisiblePositions :: MonadState GameState m => m (Set Position) +characterVisiblePositions = do + charPos <- use characterPosition + fillWithM + (memo . Memo.characterVisiblePositions) + charPos + (uses entities $ visiblePositions charPos visionRadius) + +characterVisibleEntities :: GameState -> EntityMap.EntityMap SomeEntity +characterVisibleEntities game = + let charPos = game ^. characterPosition + in visibleEntities charPos visionRadius $ game ^. entities + +positionIsCharacterVisible :: MonadState GameState m => Position -> m Bool +positionIsCharacterVisible p = (p `elem`) <$> characterVisiblePositions +-- ^ TODO optimize + +entitiesCollision + :: ( Functor f + , forall xx. MonoFoldable (f xx) + , Element (f SomeEntity) ~ SomeEntity + , Element (f (Maybe Collision)) ~ Maybe Collision + , Show (f (Maybe Collision)) + , Show (f SomeEntity) + ) + => f SomeEntity + -> Maybe Collision +entitiesCollision = join . maximumMay . fmap entityCollision + +collisionAt :: MonadState GameState m => Position -> m (Maybe Collision) +collisionAt p = uses (entities . EntityMap.atPosition p) entitiesCollision + +entitiesAtCharacter :: Lens' GameState (VectorBag SomeEntity) +entitiesAtCharacter = lens getter setter + where + getter gs = gs ^. entities . EntityMap.atPosition (gs ^. characterPosition) + setter gs ents = gs + & entities . EntityMap.atPosition (gs ^. characterPosition) .~ ents + +-- | Returns all entities at the given position that are revealed to the +-- character. +-- +-- Concretely, this is either entities that are *currently* visible to the +-- character, or entities, that are immobile and that the character has seen +-- before +revealedEntitiesAtPosition + :: MonadState GameState m + => Position + -> m (VectorBag SomeEntity) +revealedEntitiesAtPosition p = do + allRev <- use $ debugState . allRevealed + cvps <- characterVisiblePositions + entitiesAtPosition <- use $ entities . EntityMap.atPosition p + revealed <- use revealedPositions + let immobileEntitiesAtPosition = filter (not . entityCanMove) entitiesAtPosition + pure $ if | allRev || p `member` cvps + -> entitiesAtPosition + | p `member` revealed + -> immobileEntitiesAtPosition + | otherwise + -> mempty diff --git a/users/grfn/xanthous/src/Xanthous/Game/Memo.hs b/users/grfn/xanthous/src/Xanthous/Game/Memo.hs new file mode 100644 index 000000000000..9e483a8d4af7 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Memo.hs @@ -0,0 +1,52 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +-- | Memoized versions of calculations +-------------------------------------------------------------------------------- +module Xanthous.Game.Memo + ( MemoState + , emptyMemoState + , clear + -- ** Memo lenses + , characterVisiblePositions + + -- * Memoized values + , Memoized(UnMemoized) + , memoizeWith + , getMemoized + , runMemoized + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson (ToJSON, FromJSON) +import Data.Aeson.Generic.DerivingVia +import Test.QuickCheck (CoArbitrary, Function, Arbitrary) +-------------------------------------------------------------------------------- +import Xanthous.Data (Position) +import Xanthous.Data.Memo +import Xanthous.Util.QuickCheck (GenericArbitrary(GenericArbitrary)) +-------------------------------------------------------------------------------- + +-- | Memoized calculations on the game state +data MemoState = MemoState + { -- | Memoized version of 'Xanthous.Game.Lenses.characterVisiblePositions', + -- memoized with the position of the character + _characterVisiblePositions :: Memoized Position (Set Position) + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary MemoState + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + MemoState +makeLenses ''MemoState + +emptyMemoState :: MemoState +emptyMemoState = MemoState { _characterVisiblePositions = UnMemoized } +{-# INLINE emptyMemoState #-} + +clear :: Lens' MemoState (Memoized k v) -> MemoState -> MemoState +clear = flip set UnMemoized +{-# INLINE clear #-} + +{-# ANN module ("Hlint: ignore Use newtype instead of data" :: String) #-} diff --git a/users/grfn/xanthous/src/Xanthous/Game/Prompt.hs b/users/grfn/xanthous/src/Xanthous/Game/Prompt.hs new file mode 100644 index 000000000000..2d6c0a280f41 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/Prompt.hs @@ -0,0 +1,359 @@ +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE GADTs #-} +-------------------------------------------------------------------------------- +module Xanthous.Game.Prompt + ( PromptType(..) + , SPromptType(..) + , SingPromptType(..) + , PromptCancellable(..) + , PromptResult(..) + , PromptState(..) + , promptStatePosition + , MenuOption(..) + , mkMenuItems + , PromptInput + , Prompt(..) + , mkPrompt + , mkStringPrompt + , mkStringPromptWithDefault + , mkMenu + , mkPointOnMapPrompt + , mkFirePrompt + , isCancellable + , submitPrompt + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Brick.Widgets.Edit (Editor, editorText, getEditContents) +import Test.QuickCheck +import Test.QuickCheck.Arbitrary.Generic +-------------------------------------------------------------------------------- +import Xanthous.Util (smallestNotIn, AlphaChar (..)) +import Xanthous.Data (Direction, Position, Tiles) +import Xanthous.Data.App (ResourceName) +import qualified Xanthous.Data.App as Resource +-------------------------------------------------------------------------------- + +data PromptType where + StringPrompt :: PromptType + Confirm :: PromptType + Menu :: Type -> PromptType + DirectionPrompt :: PromptType + PointOnMap :: PromptType + -- | Throw an item or fire a projectile weapon. Prompt is to select the + -- direction + Fire :: PromptType + Continue :: PromptType + deriving stock (Generic) + +instance Show PromptType where + show StringPrompt = "StringPrompt" + show Confirm = "Confirm" + show (Menu _) = "Menu" + show DirectionPrompt = "DirectionPrompt" + show PointOnMap = "PointOnMap" + show Continue = "Continue" + show Fire = "Fire" + +data SPromptType :: PromptType -> Type where + SStringPrompt :: SPromptType 'StringPrompt + SConfirm :: SPromptType 'Confirm + SMenu :: SPromptType ('Menu a) + SDirectionPrompt :: SPromptType 'DirectionPrompt + SPointOnMap :: SPromptType 'PointOnMap + SContinue :: SPromptType 'Continue + SFire :: SPromptType 'Fire + +instance NFData (SPromptType pt) where + rnf SStringPrompt = () + rnf SConfirm = () + rnf SMenu = () + rnf SDirectionPrompt = () + rnf SPointOnMap = () + rnf SContinue = () + rnf SFire = () + +class SingPromptType pt where singPromptType :: SPromptType pt +instance SingPromptType 'StringPrompt where singPromptType = SStringPrompt +instance SingPromptType 'Confirm where singPromptType = SConfirm +instance SingPromptType 'DirectionPrompt where singPromptType = SDirectionPrompt +instance SingPromptType 'PointOnMap where singPromptType = SPointOnMap +instance SingPromptType 'Continue where singPromptType = SContinue +instance SingPromptType 'Fire where singPromptType = SFire + +instance Show (SPromptType pt) where + show SStringPrompt = "SStringPrompt" + show SConfirm = "SConfirm" + show SMenu = "SMenu" + show SDirectionPrompt = "SDirectionPrompt" + show SPointOnMap = "SPointOnMap" + show SContinue = "SContinue" + show SFire = "SFire" + +data PromptCancellable + = Cancellable + | Uncancellable + deriving stock (Show, Eq, Ord, Enum, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + +instance Arbitrary PromptCancellable where + arbitrary = genericArbitrary + +data PromptResult (pt :: PromptType) where + StringResult :: Text -> PromptResult 'StringPrompt + ConfirmResult :: Bool -> PromptResult 'Confirm + MenuResult :: forall a. a -> PromptResult ('Menu a) + DirectionResult :: Direction -> PromptResult 'DirectionPrompt + PointOnMapResult :: Position -> PromptResult 'PointOnMap + FireResult :: Position -> PromptResult 'Fire + ContinueResult :: PromptResult 'Continue + +instance Arbitrary (PromptResult 'StringPrompt) where + arbitrary = StringResult <$> arbitrary + +instance Arbitrary (PromptResult 'Confirm) where + arbitrary = ConfirmResult <$> arbitrary + +instance Arbitrary a => Arbitrary (PromptResult ('Menu a)) where + arbitrary = MenuResult <$> arbitrary + +instance Arbitrary (PromptResult 'DirectionPrompt) where + arbitrary = DirectionResult <$> arbitrary + +instance Arbitrary (PromptResult 'PointOnMap) where + arbitrary = PointOnMapResult <$> arbitrary + +instance Arbitrary (PromptResult 'Continue) where + arbitrary = pure ContinueResult + +instance Arbitrary (PromptResult 'Fire) where + arbitrary = FireResult <$> arbitrary + +-------------------------------------------------------------------------------- + +data PromptState pt where + StringPromptState + :: Editor Text ResourceName -> PromptState 'StringPrompt + DirectionPromptState :: PromptState 'DirectionPrompt + ContinuePromptState :: PromptState 'Continue + ConfirmPromptState :: PromptState 'Confirm + MenuPromptState :: forall a. PromptState ('Menu a) + PointOnMapPromptState :: Position -> PromptState 'PointOnMap + FirePromptState :: Position -> PromptState 'Fire + +instance NFData (PromptState pt) where + rnf sps@(StringPromptState ed) = sps `deepseq` ed `deepseq` () + rnf DirectionPromptState = () + rnf ContinuePromptState = () + rnf ConfirmPromptState = () + rnf MenuPromptState = () + rnf pomps@(PointOnMapPromptState pos) = pomps `deepseq` pos `deepseq` () + rnf fps@(FirePromptState pos) = fps `deepseq` pos `deepseq` () + +instance Arbitrary (PromptState 'StringPrompt) where + arbitrary = StringPromptState <$> arbitrary + +instance Arbitrary (PromptState 'DirectionPrompt) where + arbitrary = pure DirectionPromptState + +instance Arbitrary (PromptState 'Continue) where + arbitrary = pure ContinuePromptState + +instance Arbitrary (PromptState ('Menu a)) where + arbitrary = pure MenuPromptState + +instance Arbitrary (PromptState 'Fire) where + arbitrary = FirePromptState <$> arbitrary + +instance CoArbitrary (PromptState 'StringPrompt) where + coarbitrary (StringPromptState ed) = coarbitrary ed + +instance CoArbitrary (PromptState 'DirectionPrompt) where + coarbitrary DirectionPromptState = coarbitrary () + +instance CoArbitrary (PromptState 'Continue) where + coarbitrary ContinuePromptState = coarbitrary () + +instance CoArbitrary (PromptState ('Menu a)) where + coarbitrary MenuPromptState = coarbitrary () + +instance CoArbitrary (PromptState 'Fire) where + coarbitrary (FirePromptState pos) = coarbitrary pos + +deriving stock instance Show (PromptState pt) + +-- | Traversal over the position for the prompt types with positions in their +-- prompt state (currently 'Fire' and 'PointOnMap') +promptStatePosition :: forall pt. Traversal' (PromptState pt) Position +promptStatePosition _ ps@(StringPromptState _) = pure ps +promptStatePosition _ DirectionPromptState = pure DirectionPromptState +promptStatePosition _ ContinuePromptState = pure ContinuePromptState +promptStatePosition _ ConfirmPromptState = pure ConfirmPromptState +promptStatePosition _ MenuPromptState = pure MenuPromptState +promptStatePosition f (PointOnMapPromptState p) = PointOnMapPromptState <$> f p +promptStatePosition f (FirePromptState p) = FirePromptState <$> f p + +data MenuOption a = MenuOption Text a + deriving stock (Eq, Generic, Functor) + deriving anyclass (NFData, CoArbitrary, Function) + +instance Comonad MenuOption where + extract (MenuOption _ x) = x + extend cok mo@(MenuOption text _) = MenuOption text (cok mo) + +mkMenuItems :: (MonoFoldable f, Element f ~ (Char, MenuOption a)) + => f + -> Map Char (MenuOption a) +mkMenuItems = flip foldl' mempty $ \items (chr, option) -> + let chr' = if has (ix chr) items + then getAlphaChar . smallestNotIn . map AlphaChar $ keys items + else chr + in items & at chr' ?~ option + +instance Show (MenuOption a) where + show (MenuOption m _) = show m + +type family PromptInput (pt :: PromptType) :: Type where + PromptInput ('Menu a) = Map Char (MenuOption a) + PromptInput 'PointOnMap = Position -- Character pos + PromptInput 'Fire = (Position, Tiles) -- Nearest enemy, range + PromptInput 'StringPrompt = Maybe Text -- Default value + PromptInput _ = () + +data Prompt (m :: Type -> Type) where + Prompt + :: forall (pt :: PromptType) + (m :: Type -> Type). + PromptCancellable + -> SPromptType pt + -> PromptState pt + -> PromptInput pt + -> (PromptResult pt -> m ()) + -> Prompt m + +instance Show (Prompt m) where + show (Prompt c pt ps pri _) + = "(Prompt " + <> show c <> " " + <> show pt <> " " + <> show ps <> " " + <> showPri + <> " <function>)" + where showPri = case pt of + SMenu -> show pri + _ -> "()" + +instance NFData (Prompt m) where + rnf (Prompt c SMenu ps pri cb) + = c + `deepseq` ps + `deepseq` pri + `seq` cb + `seq` () + rnf (Prompt c spt ps pri cb) + = c + `deepseq` spt + `deepseq` ps + `deepseq` pri + `seq` cb + `seq` () + +instance CoArbitrary (m ()) => CoArbitrary (Prompt m) where + coarbitrary (Prompt c SStringPrompt ps pri cb) = + variant @Int 1 . coarbitrary (c, ps, pri, cb) + coarbitrary (Prompt c SConfirm _ pri cb) = -- TODO fill in prompt state + variant @Int 2 . coarbitrary (c, pri, cb) + coarbitrary (Prompt c SMenu _ps _pri _cb) = + variant @Int 3 . coarbitrary c {-, ps, pri, cb -} + coarbitrary (Prompt c SDirectionPrompt ps pri cb) = + variant @Int 4 . coarbitrary (c, ps, pri, cb) + coarbitrary (Prompt c SPointOnMap _ pri cb) = -- TODO fill in prompt state + variant @Int 5 . coarbitrary (c, pri, cb) + coarbitrary (Prompt c SContinue ps pri cb) = + variant @Int 6 . coarbitrary (c, ps, pri, cb) + coarbitrary (Prompt c SFire ps pri cb) = + variant @Int 7 . coarbitrary (c, ps, pri, cb) + +-- instance Function (Prompt m) where +-- function = functionMap toTuple _fromTuple +-- where +-- toTuple (Prompt c pt ps pri cb) = (c, pt, ps, pri, cb) + + +mkPrompt + :: (PromptInput pt ~ ()) + => PromptCancellable -- ^ Is the prompt cancellable or not? + -> SPromptType pt -- ^ The type of the prompt + -> (PromptResult pt -> m ()) -- ^ Function to call when the prompt is complete + -> Prompt m +mkPrompt c pt@SDirectionPrompt cb = Prompt c pt DirectionPromptState () cb +mkPrompt c pt@SContinue cb = Prompt c pt ContinuePromptState () cb +mkPrompt c pt@SConfirm cb = Prompt c pt ConfirmPromptState () cb + +mkStringPrompt + :: PromptCancellable -- ^ Is the prompt cancellable or not? + -> (PromptResult 'StringPrompt -> m ()) -- ^ Function to call when the prompt is complete + -> Prompt m +mkStringPrompt c = + let ps = StringPromptState $ editorText Resource.Prompt (Just 1) "" + in Prompt c SStringPrompt ps Nothing + +mkStringPromptWithDefault + :: PromptCancellable -- ^ Is the prompt cancellable or not? + -> Text -- ^ Default value for the prompt + -> (PromptResult 'StringPrompt -> m ()) -- ^ Function to call when the prompt is complete + -> Prompt m +mkStringPromptWithDefault c def = + let ps = StringPromptState $ editorText Resource.Prompt (Just 1) "" + in Prompt c SStringPrompt ps (Just def) + +mkMenu + :: forall a m. + PromptCancellable + -> Map Char (MenuOption a) -- ^ Menu items + -> (PromptResult ('Menu a) -> m ()) + -> Prompt m +mkMenu c = Prompt c SMenu MenuPromptState + +mkPointOnMapPrompt + :: PromptCancellable + -> Position + -> (PromptResult 'PointOnMap -> m ()) + -> Prompt m +mkPointOnMapPrompt c pos = Prompt c SPointOnMap (PointOnMapPromptState pos) pos + +mkFirePrompt + :: PromptCancellable + -> Position -- ^ Initial position + -> Tiles -- ^ Range + -> (PromptResult 'Fire -> m ()) + -> Prompt m +mkFirePrompt c pos range = Prompt c SFire (FirePromptState pos) (pos, range) + +isCancellable :: Prompt m -> Bool +isCancellable (Prompt Cancellable _ _ _ _) = True +isCancellable (Prompt Uncancellable _ _ _ _) = False + +submitPrompt :: Applicative m => Prompt m -> m () +submitPrompt (Prompt _ pt ps pri cb) = + case (pt, ps, pri) of + (SStringPrompt, StringPromptState edit, mDef) -> + let inputVal = mconcat . getEditContents $ edit + val | null inputVal, Just def <- mDef = def + | otherwise = inputVal + in cb $ StringResult val + (SDirectionPrompt, DirectionPromptState, _) -> + pure () -- Don't use submit with a direction prompt + (SContinue, ContinuePromptState, _) -> + cb ContinueResult + (SMenu, MenuPromptState, _) -> + pure () -- Don't use submit with a menu prompt + (SPointOnMap, PointOnMapPromptState pos, _) -> + cb $ PointOnMapResult pos + (SConfirm, ConfirmPromptState, _) -> + cb $ ConfirmResult True + (SFire, FirePromptState pos, _) -> + cb $ FireResult pos diff --git a/users/grfn/xanthous/src/Xanthous/Game/State.hs b/users/grfn/xanthous/src/Xanthous/Game/State.hs new file mode 100644 index 000000000000..3025eb15be5a --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Game/State.hs @@ -0,0 +1,573 @@ +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +-------------------------------------------------------------------------------- +module Xanthous.Game.State + ( GameState(..) + , entities + , levels + , revealedPositions + , messageHistory + , randomGen + , activePanel + , promptState + , characterEntityID + , autocommand + , savefile + , memo + , GamePromptState(..) + + -- * Game Level + , GameLevel(..) + , levelEntities + , upStaircasePosition + , levelRevealedPositions + + -- * Messages + , MessageHistory(..) + , HasMessages(..) + , HasTurn(..) + , HasDisplayedTurn(..) + , pushMessage + , previousMessage + , nextTurn + + -- * Autocommands + , Autocommand(..) + , AutocommandState(..) + , _NoAutocommand + , _ActiveAutocommand + + -- * App monad + , AppT(..) + , AppM + , runAppT + + -- * Entities + , Draw(..) + , Brain(..) + , Brainless(..) + , brainVia + , Collision(..) + , Entity(..) + , SomeEntity(..) + , downcastEntity + , _SomeEntity + , entityIs + , entityTypeName + + -- ** Vias + , Color(..) + , DrawNothing(..) + , DrawRawChar(..) + , DrawRawCharPriority(..) + , DrawCharacter(..) + , DrawStyledCharacter(..) + , DeriveEntity(..) + -- ** Field classes + , HasChar(..) + , HasStyle(..) + + -- * Debug State + , DebugState(..) + , debugState + , allRevealed + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.List.NonEmpty ( NonEmpty((:|))) +import qualified Data.List.NonEmpty as NonEmpty +import Data.Typeable +import Data.Coerce +import System.Random +import Test.QuickCheck +import Test.QuickCheck.Arbitrary.Generic +import Control.Monad.Random.Class +import Control.Monad.State +import Control.Monad.Trans.Control (MonadTransControl(..)) +import Control.Monad.Trans.Compose +import Control.Monad.Morph (MFunctor(..)) +import Brick (EventM, Widget, raw, str, emptyWidget) +import Data.Aeson (ToJSON(..), FromJSON(..), Value(Null)) +import qualified Data.Aeson as JSON +import Data.Aeson.Generic.DerivingVia +import Data.Generics.Product.Fields +import qualified Graphics.Vty.Attributes as Vty +import qualified Graphics.Vty.Image as Vty +-------------------------------------------------------------------------------- +import Xanthous.Util (KnownBool(..)) +import Xanthous.Util.QuickCheck (GenericArbitrary(..)) +import Xanthous.Data +import Xanthous.Data.App +import Xanthous.Data.Levels +import Xanthous.Data.EntityMap (EntityMap, EntityID) +import Xanthous.Data.EntityChar +import Xanthous.Data.VectorBag +import Xanthous.Data.Entities +import Xanthous.Orphans () +import Xanthous.Game.Prompt +import Xanthous.Game.Env +import Xanthous.Game.Memo (MemoState) +-------------------------------------------------------------------------------- + +data MessageHistory + = MessageHistory + { _messages :: Map Word (NonEmpty Text) + , _turn :: Word + , _displayedTurn :: Maybe Word + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary MessageHistory + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + MessageHistory +makeFieldsNoPrefix ''MessageHistory + +instance Semigroup MessageHistory where + (MessageHistory msgsโ turnโ dtโ) <> (MessageHistory msgsโ turnโ dtโ) = + MessageHistory (msgsโ <> msgsโ) (max turnโ turnโ) $ case (dtโ, dtโ) of + (_, Nothing) -> Nothing + (Just t, _) -> Just t + (Nothing, Just t) -> Just t + +instance Monoid MessageHistory where + mempty = MessageHistory mempty 0 Nothing + +type instance Element MessageHistory = [Text] +instance MonoFunctor MessageHistory where + omap f mh@(MessageHistory _ t _) = + mh & messages . at t %~ (NonEmpty.nonEmpty . f . toList =<<) + +instance MonoComonad MessageHistory where + oextract (MessageHistory ms t dt) = maybe [] toList $ ms ^. at (fromMaybe t dt) + oextend cok mh@(MessageHistory _ t dt) = + mh & messages . at (fromMaybe t dt) .~ NonEmpty.nonEmpty (cok mh) + +pushMessage :: Text -> MessageHistory -> MessageHistory +pushMessage msg mh@(MessageHistory _ turn' _) = + mh + & messages . at turn' %~ \case + Nothing -> Just $ msg :| mempty + Just msgs -> Just $ msg <| msgs + & displayedTurn .~ Nothing + +nextTurn :: MessageHistory -> MessageHistory +nextTurn = (turn +~ 1) . (displayedTurn .~ Nothing) + +previousMessage :: MessageHistory -> MessageHistory +previousMessage mh = mh & displayedTurn .~ maximumOf + (messages . ifolded . asIndex . filtered (< mh ^. turn)) + mh + + +-------------------------------------------------------------------------------- + +data GamePromptState m where + NoPrompt :: GamePromptState m + WaitingPrompt :: Text -> Prompt m -> GamePromptState m + deriving stock (Show, Generic) + deriving anyclass (NFData) + +-- | Non-injective! We never try to serialize waiting prompts, since: +-- +-- * they contain callback functions +-- * we can't save the game when in a prompt anyway +instance ToJSON (GamePromptState m) where + toJSON _ = Null + +-- | Always expects Null +instance FromJSON (GamePromptState m) where + parseJSON Null = pure NoPrompt + parseJSON _ = fail "Invalid GamePromptState; expected null" + +instance CoArbitrary (GamePromptState m) where + coarbitrary NoPrompt = variant @Int 1 + coarbitrary (WaitingPrompt txt _) = variant @Int 2 . coarbitrary txt + +instance Function (GamePromptState m) where + function = functionMap onlyNoPrompt (const NoPrompt) + where + onlyNoPrompt NoPrompt = () + onlyNoPrompt (WaitingPrompt _ _) = + error "Can't handle prompts in Function!" + +-------------------------------------------------------------------------------- + +newtype AppT m a + = AppT { unAppT :: ReaderT GameEnv (StateT GameState m) a } + deriving ( Functor + , Applicative + , Monad + , MonadState GameState + , MonadReader GameEnv + , MonadIO + ) + via (ReaderT GameEnv (StateT GameState m)) + deriving ( MonadTrans + , MFunctor + ) + via (ReaderT GameEnv `ComposeT` StateT GameState) + +type AppM = AppT (EventM ResourceName) + +-------------------------------------------------------------------------------- + +class Draw a where + drawWithNeighbors :: Neighbors (VectorBag SomeEntity) -> a -> Widget n + drawWithNeighbors = const draw + + draw :: a -> Widget n + draw = drawWithNeighbors $ pure mempty + + -- | higher priority gets drawn on top + drawPriority :: a -> Word + drawPriority = const minBound + +instance Draw a => Draw (Positioned a) where + drawWithNeighbors ns (Positioned _ a) = drawWithNeighbors ns a + draw (Positioned _ a) = draw a + +newtype DrawCharacter (char :: Symbol) (a :: Type) where + DrawCharacter :: a -> DrawCharacter char a + +instance KnownSymbol char => Draw (DrawCharacter char a) where + draw _ = str $ symbolVal @char Proxy + +data Color = Black | Red | Green | Yellow | Blue | Magenta | Cyan | White + +class KnownColor (color :: Color) where + colorVal :: forall proxy. proxy color -> Vty.Color + +instance KnownColor 'Black where colorVal _ = Vty.black +instance KnownColor 'Red where colorVal _ = Vty.red +instance KnownColor 'Green where colorVal _ = Vty.green +instance KnownColor 'Yellow where colorVal _ = Vty.yellow +instance KnownColor 'Blue where colorVal _ = Vty.blue +instance KnownColor 'Magenta where colorVal _ = Vty.magenta +instance KnownColor 'Cyan where colorVal _ = Vty.cyan +instance KnownColor 'White where colorVal _ = Vty.white + +class KnownMaybeColor (maybeColor :: Maybe Color) where + maybeColorVal :: forall proxy. proxy maybeColor -> Maybe Vty.Color + +instance KnownMaybeColor 'Nothing where maybeColorVal _ = Nothing +instance KnownColor color => KnownMaybeColor ('Just color) where + maybeColorVal _ = Just $ colorVal @color Proxy + +newtype DrawStyledCharacter (fg :: Maybe Color) (bg :: Maybe Color) (char :: Symbol) (a :: Type) where + DrawStyledCharacter :: a -> DrawStyledCharacter fg bg char a + +instance + ( KnownMaybeColor fg + , KnownMaybeColor bg + , KnownSymbol char + ) + => Draw (DrawStyledCharacter fg bg char a) where + draw _ = raw $ Vty.string attr $ symbolVal @char Proxy + where attr = Vty.Attr + { Vty.attrStyle = Vty.Default + , Vty.attrForeColor = maybe Vty.Default Vty.SetTo + $ maybeColorVal @fg Proxy + , Vty.attrBackColor = maybe Vty.Default Vty.SetTo + $ maybeColorVal @bg Proxy + , Vty.attrURL = Vty.Default + } + +instance Draw EntityChar where + draw EntityChar{..} = raw $ Vty.string _style [_char] + +-------------------------------------------------------------------------------- + +newtype DrawNothing (a :: Type) = DrawNothing a + +instance Draw (DrawNothing a) where + draw = const emptyWidget + drawPriority = const 0 + +newtype DrawRawChar (rawField :: Symbol) (a :: Type) = DrawRawChar a + +instance + forall rawField a raw. + ( HasField rawField a a raw raw + , HasChar raw EntityChar + ) => Draw (DrawRawChar rawField a) where + draw (DrawRawChar e) = draw $ e ^. field @rawField . char + +newtype DrawRawCharPriority + (rawField :: Symbol) + (priority :: Nat) + (a :: Type) + = DrawRawCharPriority a + +instance + forall rawField priority a raw. + ( HasField rawField a a raw raw + , KnownNat priority + , HasChar raw EntityChar + ) => Draw (DrawRawCharPriority rawField priority a) where + draw (DrawRawCharPriority e) = draw $ e ^. field @rawField . char + drawPriority = const . fromIntegral $ natVal @priority Proxy + + +-------------------------------------------------------------------------------- + +class Brain a where + step :: Ticks -> Positioned a -> AppM (Positioned a) + -- | Does this entity ever move on its own? + entityCanMove :: a -> Bool + entityCanMove = const False + +newtype Brainless a = Brainless a + +instance Brain (Brainless a) where + step = const pure + +-- | Workaround for the inability to use DerivingVia on Brain due to the lack of +-- higher-order roles (specifically AppT not having its last type argument have +-- role representational bc of StateT) +brainVia + :: forall brain entity. (Coercible entity brain, Brain brain) + => (entity -> brain) -- ^ constructor, ignored + -> (Ticks -> Positioned entity -> AppM (Positioned entity)) +brainVia _ ticks = fmap coerce . step ticks . coerce @_ @(Positioned brain) + +-------------------------------------------------------------------------------- + +class ( Show a, Eq a, Ord a, NFData a + , ToJSON a, FromJSON a + , Draw a, Brain a + ) => Entity a where + entityAttributes :: a -> EntityAttributes + entityAttributes = const defaultEntityAttributes + description :: a -> Text + entityChar :: a -> EntityChar + entityCollision :: a -> Maybe Collision + entityCollision = const $ Just Stop + +data SomeEntity where + SomeEntity :: forall a. (Entity a, Typeable a) => a -> SomeEntity + +instance Show SomeEntity where + show (SomeEntity e) = "SomeEntity (" <> show e <> ")" + +instance Eq SomeEntity where + (SomeEntity (a :: ea)) == (SomeEntity (b :: eb)) = case eqT @ea @eb of + Just Refl -> a == b + _ -> False + +instance Ord SomeEntity where + compare (SomeEntity (a :: ea)) (SomeEntity (b :: eb)) = case eqT @ea @eb of + Just Refl -> compare a b + _ -> compare (typeRep $ Proxy @ea) (typeRep $ Proxy @eb) + + +instance NFData SomeEntity where + rnf (SomeEntity ent) = ent `deepseq` () + +instance ToJSON SomeEntity where + toJSON (SomeEntity ent) = entityToJSON ent + where + entityToJSON :: forall entity. (Entity entity, Typeable entity) + => entity -> JSON.Value + entityToJSON entity = JSON.object + [ "type" JSON..= tshow (typeRep @_ @entity Proxy) + , "data" JSON..= toJSON entity + ] + +instance Draw SomeEntity where + drawWithNeighbors ns (SomeEntity ent) = drawWithNeighbors ns ent + drawPriority (SomeEntity ent) = drawPriority ent + +instance Brain SomeEntity where + step ticks (Positioned p (SomeEntity ent)) = + fmap SomeEntity <$> step ticks (Positioned p ent) + entityCanMove (SomeEntity ent) = entityCanMove ent + +downcastEntity :: forall (a :: Type). (Typeable a) => SomeEntity -> Maybe a +downcastEntity (SomeEntity e) = cast e + +entityIs :: forall (a :: Type). (Typeable a) => SomeEntity -> Bool +entityIs = isJust . downcastEntity @a + +_SomeEntity :: forall a. (Entity a, Typeable a) => Prism' SomeEntity a +_SomeEntity = prism' SomeEntity downcastEntity + +-- | Get the name of the type of 'SomeEntity' as a string +entityTypeName :: SomeEntity -> Text +entityTypeName (SomeEntity e) = pack . tyConName . typeRepTyCon $ typeOf e + +newtype DeriveEntity + (blocksVision :: Bool) + (description :: Symbol) + (entityChar :: Symbol) + (entity :: Type) + = DeriveEntity entity + deriving newtype (Show, Eq, Ord, NFData, ToJSON, FromJSON, Draw) + +instance Brain entity => Brain (DeriveEntity b d c entity) where + step = brainVia $ \(DeriveEntity e) -> e + +instance + ( KnownBool blocksVision + , KnownSymbol description + , KnownSymbol entityChar + , Show entity, Eq entity, Ord entity, NFData entity + , ToJSON entity, FromJSON entity + , Draw entity, Brain entity + ) + => Entity (DeriveEntity blocksVision description entityChar entity) where + entityAttributes _ = defaultEntityAttributes + & blocksVision .~ boolVal @blocksVision + description _ = pack . symbolVal $ Proxy @description + entityChar _ = fromString . symbolVal $ Proxy @entityChar + +-------------------------------------------------------------------------------- + +data GameLevel = GameLevel + { _levelEntities :: !(EntityMap SomeEntity) + , _upStaircasePosition :: !Position + , _levelRevealedPositions :: !(Set Position) + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) + deriving (ToJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + GameLevel + +-------------------------------------------------------------------------------- + +data Autocommand + = AutoMove Direction + | AutoRest + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (NFData, Hashable, ToJSON, FromJSON, CoArbitrary, Function) + deriving Arbitrary via GenericArbitrary Autocommand +{-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-} + +data AutocommandState + = NoAutocommand + | ActiveAutocommand Autocommand (Async ()) + deriving stock (Eq, Ord, Generic) + deriving anyclass (Hashable) + +instance Show AutocommandState where + show NoAutocommand = "NoAutocommand" + show (ActiveAutocommand ac _) = + "(ActiveAutocommand " <> show ac <> " <Async>)" + +instance ToJSON AutocommandState where + toJSON = const Null + +instance FromJSON AutocommandState where + parseJSON Null = pure NoAutocommand + parseJSON _ = fail "Invalid AutocommandState; expected null" + +instance NFData AutocommandState where + rnf NoAutocommand = () + rnf (ActiveAutocommand ac t) = ac `deepseq` t `seq` () + +instance CoArbitrary AutocommandState where + coarbitrary NoAutocommand = variant @Int 1 + coarbitrary (ActiveAutocommand ac t) + = variant @Int 2 + . coarbitrary ac + . coarbitrary (hash t) + +instance Function AutocommandState where + function = functionMap onlyNoAC (const NoAutocommand) + where + onlyNoAC NoAutocommand = () + onlyNoAC _ = error "Can't handle autocommands in Function" + +-------------------------------------------------------------------------------- + + +data DebugState = DebugState + { _allRevealed :: !Bool + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving (ToJSON, FromJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + DebugState +{-# ANN DebugState ("HLint: ignore Use newtype instead of data" :: String) #-} + +instance Arbitrary DebugState where + arbitrary = genericArbitrary + +data GameState = GameState + { _levels :: !(Levels GameLevel) + , _characterEntityID :: !EntityID + , _messageHistory :: !MessageHistory + , _randomGen :: !StdGen + + -- | The active panel displayed in the UI, if any + , _activePanel :: !(Maybe Panel) + + , _promptState :: !(GamePromptState AppM) + , _debugState :: !DebugState + , _autocommand :: !AutocommandState + + -- | The path to the savefile that was loaded for this game, if any + , _savefile :: !(Maybe FilePath) + + , _memo :: MemoState + } + deriving stock (Show, Generic) + deriving anyclass (NFData) + deriving (ToJSON) + via WithOptions '[ FieldLabelModifier '[Drop 1] ] + GameState + +makeLenses ''GameLevel +makeLenses ''GameState + +entities :: Lens' GameState (EntityMap SomeEntity) +entities = levels . current . levelEntities + +revealedPositions :: Lens' GameState (Set Position) +revealedPositions = levels . current . levelRevealedPositions + +instance Eq GameState where + (==) = (==) `on` \gs -> + ( gs ^. entities + , gs ^. revealedPositions + , gs ^. characterEntityID + , gs ^. messageHistory + , gs ^. activePanel + , gs ^. debugState + ) + +-------------------------------------------------------------------------------- + +runAppT :: Monad m => AppT m a -> GameEnv -> GameState -> m (a, GameState) +runAppT appt env initialState + = flip runStateT initialState + . flip runReaderT env + . unAppT + $ appt + +instance (Monad m) => MonadRandom (AppT m) where + getRandomR rng = randomGen %%= randomR rng + getRandom = randomGen %%= random + getRandomRs rng = uses randomGen $ randomRs rng + getRandoms = uses randomGen randoms + +instance MonadTransControl AppT where + type StT AppT a = (a, GameState) + liftWith f + = AppT + . ReaderT $ \e + -> StateT $ \s + -> (,s) <$> f (\action -> runAppT action e s) + restoreT = AppT . ReaderT . const . StateT . const + +-------------------------------------------------------------------------------- + +makeLenses ''DebugState +makePrisms ''AutocommandState diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level.hs new file mode 100644 index 000000000000..fc57402e7d8e --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level.hs @@ -0,0 +1,172 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level + ( generate + , Generator(..) + , SGenerator(..) + , GeneratorInput(..) + , generateFromInput + , parseGeneratorInput + , showCells + , Level(..) + , levelWalls + , levelItems + , levelCreatures + , levelDoors + , levelCharacterPosition + , levelTutorialMessage + , levelExtra + , generateLevel + , levelToEntityMap + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Data.Array.Unboxed +import qualified Options.Applicative as Opt +import Control.Monad.Random +-------------------------------------------------------------------------------- +import qualified Xanthous.Generators.Level.CaveAutomata as CaveAutomata +import qualified Xanthous.Generators.Level.Dungeon as Dungeon +import Xanthous.Generators.Level.Util +import Xanthous.Generators.Level.LevelContents +import Xanthous.Generators.Level.Village as Village +import Xanthous.Data (Dimensions, Position'(Position), Position) +import Xanthous.Data.EntityMap (EntityMap, _EntityMap) +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Entities.Environment +import Xanthous.Entities.Item (Item) +import Xanthous.Entities.Creature (Creature) +import Xanthous.Game.State (SomeEntity(..)) +import Linear.V2 +-------------------------------------------------------------------------------- + +data Generator + = CaveAutomata + | Dungeon + deriving stock (Show, Eq) + +data SGenerator (gen :: Generator) where + SCaveAutomata :: SGenerator 'CaveAutomata + SDungeon :: SGenerator 'Dungeon + +type family Params (gen :: Generator) :: Type where + Params 'CaveAutomata = CaveAutomata.Params + Params 'Dungeon = Dungeon.Params + +generate + :: RandomGen g + => SGenerator gen + -> Params gen + -> Dimensions + -> g + -> Cells +generate SCaveAutomata = CaveAutomata.generate +generate SDungeon = Dungeon.generate + +data GeneratorInput where + GeneratorInput :: forall gen. SGenerator gen -> Params gen -> GeneratorInput + +generateFromInput :: RandomGen g => GeneratorInput -> Dimensions -> g -> Cells +generateFromInput (GeneratorInput sg ps) = generate sg ps + +parseGeneratorInput :: Opt.Parser GeneratorInput +parseGeneratorInput = Opt.subparser + $ generatorCommand SCaveAutomata + "cave" + "Cellular-automata based cave generator" + CaveAutomata.parseParams + <> generatorCommand SDungeon + "dungeon" + "Classic dungeon map generator" + Dungeon.parseParams + where + generatorCommand sgen name desc parseParams = + Opt.command name + (Opt.info + (GeneratorInput sgen <$> parseParams) + (Opt.progDesc desc) + ) + + +showCells :: Cells -> Text +showCells arr = + let (V2 minX minY, V2 maxX maxY) = bounds arr + showCellVal True = "x" + showCellVal False = " " + showCell = showCellVal . (arr !) + row r = foldMap (showCell . (`V2` r)) [minX..maxX] + rows = row <$> [minY..maxY] + in intercalate "\n" rows + +cellsToWalls :: Cells -> EntityMap Wall +cellsToWalls cells = foldl' maybeInsertWall mempty . assocs $ cells + where + maybeInsertWall em (pos@(V2 x y), True) + | not (surroundedOnAllSides pos) = + let x' = fromIntegral x + y' = fromIntegral y + in EntityMap.insertAt (Position x' y') Wall em + maybeInsertWall em _ = em + surroundedOnAllSides pos = numAliveNeighbors cells pos == 8 + +-------------------------------------------------------------------------------- + +data Level = Level + { _levelWalls :: !(EntityMap Wall) + , _levelDoors :: !(EntityMap Door) + , _levelItems :: !(EntityMap Item) + , _levelCreatures :: !(EntityMap Creature) + , _levelTutorialMessage :: !(EntityMap GroundMessage) + , _levelStaircases :: !(EntityMap Staircase) + , _levelExtra :: !(EntityMap SomeEntity) -- ^ TODO this is a bit of a hack... + , _levelCharacterPosition :: !Position + } + deriving stock (Generic) + deriving anyclass (NFData) +makeLenses ''Level + +generateLevel + :: MonadRandom m + => SGenerator gen + -> Params gen + -> Dimensions + -> Word -- ^ Level number, starting at 0 + -> m Level +generateLevel gen ps dims num = do + rand <- mkStdGen <$> getRandom + let cells = generate gen ps dims rand + _levelWalls = cellsToWalls cells + village <- generateVillage cells gen + let _levelExtra = village + _levelItems <- randomItems cells + _levelCreatures <- randomCreatures num cells + _levelDoors <- randomDoors cells + _levelCharacterPosition <- chooseCharacterPosition cells + let upStaircase = _EntityMap # [(_levelCharacterPosition, UpStaircase)] + downStaircase <- placeDownStaircase cells + let _levelStaircases = upStaircase <> downStaircase + _levelTutorialMessage <- + if num == 0 + then tutorialMessage cells _levelCharacterPosition + else pure mempty + pure Level {..} + +levelToEntityMap :: Level -> EntityMap SomeEntity +levelToEntityMap level + = (SomeEntity <$> level ^. levelWalls) + <> (SomeEntity <$> level ^. levelDoors) + <> (SomeEntity <$> level ^. levelItems) + <> (SomeEntity <$> level ^. levelCreatures) + <> (SomeEntity <$> level ^. levelTutorialMessage) + <> (SomeEntity <$> level ^. levelStaircases) + <> (level ^. levelExtra) + +generateVillage + :: MonadRandom m + => Cells -- ^ Wall positions + -> SGenerator gen + -> m (EntityMap SomeEntity) +generateVillage wallPositions SCaveAutomata = Village.fromCave wallPositions +generateVillage _ _ = pure mempty diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level/CaveAutomata.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level/CaveAutomata.hs new file mode 100644 index 000000000000..03d534ca39b3 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level/CaveAutomata.hs @@ -0,0 +1,112 @@ +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.CaveAutomata + ( Params(..) + , defaultParams + , parseParams + , generate + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Control.Monad.Random (RandomGen, runRandT) +import Data.Array.ST +import Data.Array.Unboxed +import qualified Options.Applicative as Opt +-------------------------------------------------------------------------------- +import Xanthous.Util (between) +import Xanthous.Util.Optparse +import Xanthous.Data (Dimensions, width, height) +import Xanthous.Generators.Level.Util +import Linear.V2 +-------------------------------------------------------------------------------- + +data Params = Params + { _aliveStartChance :: Double + , _birthLimit :: Word + , _deathLimit :: Word + , _steps :: Word + } + deriving stock (Show, Eq, Generic) +makeLenses ''Params + +defaultParams :: Params +defaultParams = Params + { _aliveStartChance = 0.6 + , _birthLimit = 3 + , _deathLimit = 4 + , _steps = 4 + } + +parseParams :: Opt.Parser Params +parseParams = Params + <$> Opt.option parseChance + ( Opt.long "alive-start-chance" + <> Opt.value (defaultParams ^. aliveStartChance) + <> Opt.showDefault + <> Opt.help ( "Chance for each cell to start alive at the beginning of " + <> "the cellular automata" + ) + <> Opt.metavar "CHANCE" + ) + <*> Opt.option parseNeighbors + ( Opt.long "birth-limit" + <> Opt.value (defaultParams ^. birthLimit) + <> Opt.showDefault + <> Opt.help "Minimum neighbor count required for birth of a cell" + <> Opt.metavar "NEIGHBORS" + ) + <*> Opt.option parseNeighbors + ( Opt.long "death-limit" + <> Opt.value (defaultParams ^. deathLimit) + <> Opt.showDefault + <> Opt.help "Maximum neighbor count required for death of a cell" + <> Opt.metavar "NEIGHBORS" + ) + <*> Opt.option Opt.auto + ( Opt.long "steps" + <> Opt.value (defaultParams ^. steps) + <> Opt.showDefault + <> Opt.help "Number of generations to run the automata for" + <> Opt.metavar "STEPS" + ) + <**> Opt.helper + where + parseChance = readWithGuard + (between 0 1) + $ \res -> "Chance must be in the range [0,1], got: " <> show res + + parseNeighbors = readWithGuard + (between 0 8) + $ \res -> "Neighbors must be in the range [0,8], got: " <> show res + +generate :: RandomGen g => Params -> Dimensions -> g -> Cells +generate params dims gen + = runSTUArray + $ fmap fst + $ flip runRandT gen + $ generate' params dims + +generate' :: RandomGen g => Params -> Dimensions -> CellM g s (MCells s) +generate' params dims = do + cells <- randInitialize dims $ params ^. aliveStartChance + let steps' = params ^. steps + when (steps' > 0) + $ for_ [0 .. pred steps'] . const $ stepAutomata cells dims params + -- Remove all but the largest contiguous region of unfilled space + (_: smallerRegions) <- lift $ regions @UArray . amap not <$> freeze cells + lift $ fillAllM (fold smallerRegions) cells + lift $ fillOuterEdgesM cells + pure cells + +stepAutomata :: forall s g. MCells s -> Dimensions -> Params -> CellM g s () +stepAutomata cells dims params = do + origCells <- lift $ cloneMArray @_ @(STUArray s) cells + for_ (range (0, V2 (dims ^. width) (dims ^. height))) $ \pos -> do + neighs <- lift $ numAliveNeighborsM origCells pos + origValue <- lift $ readArray origCells pos + lift . writeArray cells pos + $ if origValue + then neighs >= params ^. deathLimit + else neighs > params ^. birthLimit diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level/Dungeon.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level/Dungeon.hs new file mode 100644 index 000000000000..0be7c0435c5a --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level/Dungeon.hs @@ -0,0 +1,190 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.Dungeon + ( Params(..) + , defaultParams + , parseParams + , generate + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding ((:>)) +-------------------------------------------------------------------------------- +import Control.Monad.Random +import Data.Array.ST +import Data.Array.IArray (amap) +import Data.Stream.Infinite (Stream(..)) +import qualified Data.Stream.Infinite as Stream +import qualified Data.Graph.Inductive.Graph as Graph +import Data.Graph.Inductive.PatriciaTree +import qualified Data.List.NonEmpty as NE +import Data.Maybe (fromJust) +import Linear.V2 +import Linear.Metric +import qualified Options.Applicative as Opt +-------------------------------------------------------------------------------- +import Xanthous.Random +import Xanthous.Data hiding (x, y, _x, _y, edges, distance) +import Xanthous.Generators.Level.Util +import Xanthous.Util.Graphics (delaunay, straightLine) +import Xanthous.Util.Graph (mstSubGraph) +-------------------------------------------------------------------------------- + +data Params = Params + { _numRoomsRange :: (Word, Word) + , _roomDimensionRange :: (Word, Word) + , _connectednessRatioRange :: (Double, Double) + } + deriving stock (Show, Eq, Ord, Generic) +makeLenses ''Params + +defaultParams :: Params +defaultParams = Params + { _numRoomsRange = (6, 8) + , _roomDimensionRange = (3, 12) + , _connectednessRatioRange = (0.1, 0.15) + } + +parseParams :: Opt.Parser Params +parseParams = Params + <$> parseRange + "num-rooms" + "number of rooms to generate in the dungeon" + "ROOMS" + (defaultParams ^. numRoomsRange) + <*> parseRange + "room-size" + "size in tiles of one of the sides of a room" + "TILES" + (defaultParams ^. roomDimensionRange) + <*> parseRange + "connectedness-ratio" + ( "ratio of edges from the delaunay triangulation to re-add to the " + <> "minimum-spanning-tree") + "RATIO" + (defaultParams ^. connectednessRatioRange) + <**> Opt.helper + where + parseRange name desc metavar (defMin, defMax) = + (,) + <$> Opt.option Opt.auto + ( Opt.long ("min-" <> name) + <> Opt.value defMin + <> Opt.showDefault + <> Opt.help ("Minimum " <> desc) + <> Opt.metavar metavar + ) + <*> Opt.option Opt.auto + ( Opt.long ("max-" <> name) + <> Opt.value defMax + <> Opt.showDefault + <> Opt.help ("Maximum " <> desc) + <> Opt.metavar metavar + ) + +generate :: RandomGen g => Params -> Dimensions -> g -> Cells +generate params dims gen + = amap not + $ runSTUArray + $ fmap fst + $ flip runRandT gen + $ generate' params dims + +-------------------------------------------------------------------------------- + +generate' :: RandomGen g => Params -> Dimensions -> CellM g s (MCells s) +generate' params dims = do + cells <- initializeEmpty dims + rooms <- genRooms params dims + for_ rooms $ fillRoom cells + + let fullRoomGraph = delaunayRoomGraph rooms + mst = mstSubGraph fullRoomGraph + mstEdges = Graph.edges mst + nonMSTEdges = filter (\(nโ, nโ, _) -> (nโ, nโ) `notElem` mstEdges) + $ Graph.labEdges fullRoomGraph + + reintroEdgeCount <- floor . (* fromIntegral (length nonMSTEdges)) + <$> getRandomR (params ^. connectednessRatioRange) + let reintroEdges = take reintroEdgeCount nonMSTEdges + corridorGraph = Graph.insEdges reintroEdges mst + + corridors <- traverse + ( uncurry corridorBetween + . over both (fromJust . Graph.lab corridorGraph) + ) $ Graph.edges corridorGraph + + for_ (join corridors) $ \pt -> lift $ writeArray cells pt True + + pure cells + +type Room = Box Word + +genRooms :: MonadRandom m => Params -> Dimensions -> m [Room] +genRooms params dims = do + numRooms <- fromIntegral <$> getRandomR (params ^. numRoomsRange) + subRand . fmap (Stream.take numRooms . removeIntersecting []) . infinitely $ do + roomWidth <- getRandomR $ params ^. roomDimensionRange + roomHeight <- getRandomR $ params ^. roomDimensionRange + xPos <- getRandomR (0, dims ^. width - roomWidth) + yPos <- getRandomR (0, dims ^. height - roomHeight) + pure Box + { _topLeftCorner = V2 xPos yPos + , _dimensions = V2 roomWidth roomHeight + } + where + removeIntersecting seen (room :> rooms) + | any (boxIntersects room) seen + = removeIntersecting seen rooms + | otherwise + = room :> removeIntersecting (room : seen) rooms + streamRepeat x = x :> streamRepeat x + infinitely = sequence . streamRepeat + +delaunayRoomGraph :: [Room] -> Gr Room Double +delaunayRoomGraph rooms = + Graph.insEdges edges . Graph.insNodes nodes $ Graph.empty + where + edges = map (\((nโ, roomโ), (nโ, roomโ)) -> (nโ, nโ, roomDist roomโ roomโ)) + . over (mapped . both) snd + . delaunay @Double + . NE.fromList + . map (\p@(_, room) -> (boxCenter $ fromIntegral <$> room, p)) + $ nodes + nodes = zip [0..] rooms + roomDist = distance `on` (boxCenter . fmap fromIntegral) + +fillRoom :: MCells s -> Room -> CellM g s () +fillRoom cells room = + let V2 posx posy = room ^. topLeftCorner + V2 dimx dimy = room ^. dimensions + in for_ [posx .. posx + dimx] $ \x -> + for_ [posy .. posy + dimy] $ \y -> + lift $ writeArray cells (V2 x y) True + +corridorBetween :: MonadRandom m => Room -> Room -> m [V2 Word] +corridorBetween originRoom destinationRoom + = straightLine <$> origin <*> destination + where + origin = choose . NE.fromList =<< originEdge + destination = choose . NE.fromList =<< destinationEdge + originEdge = pickEdge originRoom originCorner + destinationEdge = pickEdge destinationRoom destinationCorner + pickEdge room corner = choose . over both (boxEdge room) $ cornerEdges corner + originCorner = + case ( compare (originRoom ^. topLeftCorner . _x) + (destinationRoom ^. topLeftCorner . _x) + , compare (originRoom ^. topLeftCorner . _y) + (destinationRoom ^. topLeftCorner . _y) + ) of + (LT, LT) -> BottomRight + (LT, GT) -> TopRight + (GT, LT) -> BottomLeft + (GT, GT) -> TopLeft + + (EQ, LT) -> BottomLeft + (EQ, GT) -> TopRight + (GT, EQ) -> TopLeft + (LT, EQ) -> BottomRight + (EQ, EQ) -> TopLeft -- should never happen + + destinationCorner = opposite originCorner diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level/LevelContents.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level/LevelContents.hs new file mode 100644 index 000000000000..4f8a2f42ee16 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level/LevelContents.hs @@ -0,0 +1,182 @@ +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.LevelContents + ( chooseCharacterPosition + , randomItems + , randomCreatures + , randomDoors + , placeDownStaircase + , tutorialMessage + , entityFromRaw + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (any, toList) +-------------------------------------------------------------------------------- +import Control.Monad.Random +import Data.Array.IArray (amap, bounds, rangeSize, (!)) +import qualified Data.Array.IArray as Arr +import Data.Foldable (any, toList) +import Linear.V2 +-------------------------------------------------------------------------------- +import Xanthous.Generators.Level.Util +import Xanthous.Random hiding (chance) +import qualified Xanthous.Random as Random +import Xanthous.Data + ( positionFromV2, Position, _Position + , rotations, arrayNeighbors, Neighbors(..) + , neighborPositions + ) +import Xanthous.Data.EntityMap (EntityMap, _EntityMap) +import Xanthous.Entities.Raws (rawsWithType, RawType, raw) +import qualified Xanthous.Entities.Item as Item +import Xanthous.Entities.Item (Item) +import qualified Xanthous.Entities.Creature as Creature +import Xanthous.Entities.Creature (Creature) +import Xanthous.Entities.Environment + (GroundMessage(..), Door(..), unlockedDoor, Staircase(..)) +import Xanthous.Messages (message_) +import Xanthous.Util.Graphics (circle) +import Xanthous.Entities.RawTypes +import Xanthous.Entities.Creature.Hippocampus (initialHippocampus) +import Xanthous.Entities.Common (inRightHand, asWieldedItem, wielded) +import Xanthous.Game.State (SomeEntity(SomeEntity)) +-------------------------------------------------------------------------------- + +chooseCharacterPosition :: MonadRandom m => Cells -> m Position +chooseCharacterPosition = randomPosition + +randomItems :: MonadRandom m => Cells -> m (EntityMap Item) +randomItems = randomEntities (fmap Identity . Item.newWithType) (0.0004, 0.001) + +placeDownStaircase :: MonadRandom m => Cells -> m (EntityMap Staircase) +placeDownStaircase cells = do + pos <- randomPosition cells + pure $ _EntityMap # [(pos, DownStaircase)] + +randomDoors :: MonadRandom m => Cells -> m (EntityMap Door) +randomDoors cells = do + doorRatio <- getRandomR subsetRange + let numDoors = floor $ doorRatio * fromIntegral (length candidateCells) + doorPositions = + removeAdjacent . fmap positionFromV2 . take numDoors $ candidateCells + doors = zip doorPositions $ repeat unlockedDoor + pure $ _EntityMap # doors + where + removeAdjacent = + foldr (\pos acc -> + if pos `elem` (acc >>= toList . neighborPositions) + then acc + else pos : acc + ) [] + candidateCells = filter doorable $ Arr.indices cells + subsetRange = (0.8 :: Double, 1.0) + doorable pos = + not (fromMaybe True $ cells ^? ix pos) + && any (teeish . fmap (fromMaybe True)) + (rotations $ arrayNeighbors cells pos) + -- only generate doors at the *ends* of hallways, eg (where O is walkable, + -- X is a wall, and D is a door): + -- + -- O O O + -- X D X + -- O + teeish (fmap not -> (Neighbors tl t tr l r _ b _ )) = + and [tl, t, tr, b] && (and . fmap not) [l, r] + +randomCreatures + :: MonadRandom m + => Word -- ^ Level number, starting at 0 + -> Cells + -> m (EntityMap Creature) +randomCreatures levelNumber + = randomEntities maybeNewCreature (0.0007, 0.002) + where + maybeNewCreature cType + | maybe True (canGenerate levelNumber) $ cType ^. generateParams + = Just <$> newCreatureWithType cType + | otherwise + = pure Nothing + +newCreatureWithType :: MonadRandom m => CreatureType -> m Creature +newCreatureWithType _creatureType = do + let _hitpoints = _creatureType ^. maxHitpoints + _hippocampus = initialHippocampus + + equipped <- fmap join + . traverse genEquipped + $ _creatureType + ^.. generateParams . _Just . equippedItem . _Just + let _inventory = maybe id (\ei -> wielded .~ inRightHand ei) (headMay equipped) mempty + pure Creature.Creature {..} + where + genEquipped cei = do + doGen <- Random.chance $ cei ^. chance + let entName = cei ^. entityName + itemType = + fromMaybe (error $ "raw \"" <> unpack entName <> "\" not of type Item") + . preview _Item + . fromMaybe (error $ "Could not find raw: " <> unpack entName) + $ raw entName + item <- Item.newWithType itemType + if doGen + then pure [fromMaybe (error $ "raw \"" <> unpack entName <> "\" not wieldable") + $ preview asWieldedItem item] + else pure [] + + +tutorialMessage :: MonadRandom m + => Cells + -> Position -- ^ CharacterPosition + -> m (EntityMap GroundMessage) +tutorialMessage cells characterPosition = do + let distance = 2 + pos <- fmap (fromMaybe (error "No valid positions for tutorial message?")) + . choose . ChooseElement + $ accessiblePositionsWithin distance cells characterPosition + msg <- message_ ["tutorial", "message1"] + pure $ _EntityMap # [(pos, GroundMessage msg)] + where + accessiblePositionsWithin :: Int -> Cells -> Position -> [Position] + accessiblePositionsWithin dist valid pos = + review _Position + <$> filter + (\pt -> not $ valid ! (fromIntegral <$> pt)) + (circle (pos ^. _Position) dist) + +randomEntities + :: forall entity raw m t. (MonadRandom m, RawType raw, Functor t, Foldable t) + => (raw -> m (t entity)) + -> (Float, Float) + -> Cells + -> m (EntityMap entity) +randomEntities newWithType sizeRange cells = + case fromNullable $ rawsWithType @raw of + Nothing -> pure mempty + Just raws -> do + let len = rangeSize $ bounds cells + (numEntities :: Int) <- + floor . (* fromIntegral len) <$> getRandomR sizeRange + entities <- for [0..numEntities] $ const $ do + pos <- randomPosition cells + r <- choose raws + entities <- newWithType r + pure $ (pos, ) <$> entities + pure $ _EntityMap # (entities >>= toList) + +randomPosition :: MonadRandom m => Cells -> m Position +randomPosition = fmap positionFromV2 . choose . impureNonNull . cellCandidates + +-- cellCandidates :: Cells -> Cells +cellCandidates :: Cells -> Set (V2 Word) +cellCandidates + -- find the largest contiguous region of cells in the cave. + = maximumBy (compare `on` length) + . fromMaybe (error "No regions generated! this should never happen.") + . fromNullable + . regions + -- cells ends up with true = wall, we want true = can put an item here + . amap not + +entityFromRaw :: MonadRandom m => EntityRaw -> m SomeEntity +entityFromRaw (Creature ct) = SomeEntity <$> newCreatureWithType ct +entityFromRaw (Item it) = SomeEntity <$> Item.newWithType it diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level/Util.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level/Util.hs new file mode 100644 index 000000000000..0008eb965c42 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level/Util.hs @@ -0,0 +1,236 @@ +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.Util + ( MCells + , Cells + , CellM + , randInitialize + , initializeEmpty + , numAliveNeighborsM + , numAliveNeighbors + , fillOuterEdgesM + , cloneMArray + , floodFill + , regions + , fillAll + , fillAllM + , fromPoints + , fromPointsM + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (Foldable, toList, for_) +-------------------------------------------------------------------------------- +import Data.Array.ST +import Data.Array.Unboxed +import Control.Monad.ST +import Control.Monad.Random +import Data.Monoid +import Data.Foldable (Foldable, toList, for_) +import qualified Data.Set as Set +import Data.Semigroup.Foldable +import Linear.V2 +-------------------------------------------------------------------------------- +import Xanthous.Util (foldlMapM', maximum1, minimum1) +import Xanthous.Data (Dimensions, width, height) +-------------------------------------------------------------------------------- + +type MCells s = STUArray s (V2 Word) Bool +type Cells = UArray (V2 Word) Bool +type CellM g s a = RandT g (ST s) a + +randInitialize :: RandomGen g => Dimensions -> Double -> CellM g s (MCells s) +randInitialize dims aliveChance = do + res <- initializeEmpty dims + for_ [0..dims ^. width] $ \i -> + for_ [0..dims ^. height] $ \j -> do + val <- (>= aliveChance) <$> getRandomR (0, 1) + lift $ writeArray res (V2 i j) val + pure res + +initializeEmpty :: RandomGen g => Dimensions -> CellM g s (MCells s) +initializeEmpty dims = + lift $ newArray (0, V2 (dims ^. width) (dims ^. height)) False + +-- | Returns the number of neighbors of the given point in the given array that +-- are True. +-- +-- Behavior if point is out-of-bounds for the array is undefined, but will not +-- error +numAliveNeighborsM + :: forall a i m + . (MArray a Bool m, Ix i, Integral i) + => a (V2 i) Bool + -> V2 i + -> m Word +numAliveNeighborsM cells pt@(V2 x y) = do + cellBounds <- getBounds cells + getSum <$> foldlMapM' + (fmap (Sum . fromIntegral . fromEnum) . boundedGet cellBounds) + neighborPositions + + where + boundedGet :: (V2 i, V2 i) -> (Int, Int) -> m Bool + boundedGet bnds _ + | not (inRange bnds pt) + = pure True + boundedGet (V2 minX minY, V2 maxX maxY) (i, j) + | (x <= minX && i < 0) + || (y <= minY && j < 0) + || (x >= maxX && i > 0) + || (y >= maxY && j > 0) + = pure True + | otherwise = + let nx = fromIntegral $ fromIntegral x + i + ny = fromIntegral $ fromIntegral y + j + in readArray cells $ V2 nx ny + +-- | Returns the number of neighbors of the given point in the given array that +-- are True. +-- +-- Behavior if point is out-of-bounds for the array is undefined, but will not +-- error +numAliveNeighbors + :: forall a i + . (IArray a Bool, Ix i, Integral i) + => a (V2 i) Bool + -> V2 i + -> Word +numAliveNeighbors cells pt@(V2 x y) = + let cellBounds = bounds cells + in getSum $ foldMap + (Sum . fromIntegral . fromEnum . boundedGet cellBounds) + neighborPositions + + where + boundedGet :: (V2 i, V2 i) -> (Int, Int) -> Bool + boundedGet bnds _ + | not (inRange bnds pt) + = True + boundedGet (V2 minX minY, V2 maxX maxY) (i, j) + | (x <= minX && i < 0) + || (y <= minY && j < 0) + || (x >= maxX && i > 0) + || (y >= maxY && j > 0) + = True + | otherwise = + let nx = fromIntegral $ fromIntegral x + i + ny = fromIntegral $ fromIntegral y + j + in cells ! V2 nx ny + +neighborPositions :: [(Int, Int)] +neighborPositions = [(i, j) | i <- [-1..1], j <- [-1..1], (i, j) /= (0, 0)] + +fillOuterEdgesM :: (MArray a Bool m, Ix i) => a (V2 i) Bool -> m () +fillOuterEdgesM arr = do + (V2 minX minY, V2 maxX maxY) <- getBounds arr + for_ (range (minX, maxX)) $ \x -> do + writeArray arr (V2 x minY) True + writeArray arr (V2 x maxY) True + for_ (range (minY, maxY)) $ \y -> do + writeArray arr (V2 minX y) True + writeArray arr (V2 maxX y) True + +cloneMArray + :: forall a a' i e m. + ( Ix i + , MArray a e m + , MArray a' e m + , IArray UArray e + ) + => a i e + -> m (a' i e) +cloneMArray = thaw @_ @UArray <=< freeze + +-------------------------------------------------------------------------------- + +-- | Flood fill a cell array starting at a point, returning a list of all the +-- (true) cell locations reachable from that point +floodFill :: forall a i. + ( IArray a Bool + , Ix i + , Enum i + , Bounded i + , Eq i + ) + => a (V2 i) Bool -- ^ array + -> (V2 i) -- ^ position + -> Set (V2 i) +floodFill = go mempty + where + go :: Set (V2 i) -> a (V2 i) Bool -> (V2 i) -> Set (V2 i) + go res arr@(bounds -> arrBounds) idx@(V2 x y) + | not (inRange arrBounds idx) = res + | not (arr ! idx) = res + | otherwise = + let neighbors + = filter (inRange arrBounds) + . filter (/= idx) + . filter (`notMember` res) + $ V2 + <$> [(if x == minBound then x else pred x) + .. + (if x == maxBound then x else succ x)] + <*> [(if y == minBound then y else pred y) + .. + (if y == maxBound then y else succ y)] + in foldl' (\r idx' -> + if arr ! idx' + then r <> (let r' = r & contains idx' .~ True + in r' `seq` go r' arr idx') + else r) + (res & contains idx .~ True) neighbors +{-# SPECIALIZE floodFill :: UArray (V2 Word) Bool -> (V2 Word) -> Set (V2 Word) #-} + +-- | Gives a list of all the disconnected regions in a cell array, represented +-- each as lists of points +regions :: forall a i. + ( IArray a Bool + , Ix i + , Enum i + , Bounded i + , Eq i + ) + => a (V2 i) Bool + -> [Set (V2 i)] +regions arr + | Just firstPoint <- findFirstPoint arr = + let region = floodFill arr firstPoint + arr' = fillAll region arr + in region : regions arr' + | otherwise = [] + where + findFirstPoint :: a (V2 i) Bool -> Maybe (V2 i) + findFirstPoint = fmap fst . headMay . filter snd . assocs +{-# SPECIALIZE regions :: UArray (V2 Word) Bool -> [Set (V2 Word)] #-} + +fillAll :: (IArray a Bool, Ix i, Foldable f) => f i -> a i Bool -> a i Bool +fillAll ixes a = accum (const fst) a $ (, (False, ())) <$> toList ixes + +fillAllM :: (MArray a Bool m, Ix i, Foldable f) => f i -> a i Bool -> m () +fillAllM ixes a = for_ ixes $ \i -> writeArray a i False + +fromPoints + :: forall a f i. + ( IArray a Bool + , Ix i + , Functor f + , Foldable1 f + ) + => f (i, i) + -> a (i, i) Bool +fromPoints points = + let pts = Set.fromList $ toList points + dims = ( (minimum1 $ fst <$> points, minimum1 $ snd <$> points) + , (maximum1 $ fst <$> points, maximum1 $ snd <$> points) + ) + in array dims $ range dims <&> \i -> (i, i `member` pts) + +fromPointsM + :: (MArray a Bool m, Ix i, Element f ~ i, MonoFoldable f) + => NonNull f + -> m (a i Bool) +fromPointsM points = do + arr <- newArray (minimum points, maximum points) False + fillAllM (otoList points) arr + pure arr diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Level/Village.hs b/users/grfn/xanthous/src/Xanthous/Generators/Level/Village.hs new file mode 100644 index 000000000000..ab7de95e6806 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Level/Village.hs @@ -0,0 +1,126 @@ +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.Village + ( fromCave + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (any, failing, toList) +-------------------------------------------------------------------------------- +import Control.Monad.Random (MonadRandom) +import Control.Monad.State (execStateT, MonadState, modify) +import Control.Monad.Trans.Maybe +import Control.Parallel.Strategies +import Data.Array.IArray +import Data.Foldable (any, toList) +-------------------------------------------------------------------------------- +import Xanthous.Data +import Xanthous.Data.EntityMap (EntityMap) +import qualified Xanthous.Data.EntityMap as EntityMap +import Xanthous.Entities.Environment +import Xanthous.Generators.Level.Util +import Xanthous.Game.State (SomeEntity(..)) +import Xanthous.Random +-------------------------------------------------------------------------------- + +fromCave :: MonadRandom m + => Cells -- ^ The positions of all the walls + -> m (EntityMap SomeEntity) +fromCave wallPositions = execStateT (fromCave' wallPositions) mempty + +fromCave' :: forall m. (MonadRandom m, MonadState (EntityMap SomeEntity) m) + => Cells + -> m () +fromCave' wallPositions = failing (pure ()) $ do + Just villageRegion <- + choose + . (`using` parTraversable rdeepseq) + . weightedBy (\reg -> let circSize = length $ circumference reg + in if circSize == 50 + then (1.0 :: Double) + else 1.0 / (fromIntegral . abs $ circSize - 50)) + $ regions closedHallways + + let circ = setFromList . circumference $ villageRegion + + centerPoints <- chooseSubset (0.1 :: Double) $ toList circ + + roomTiles <- foldM + (flip $ const $ stepOut circ) + (map pure centerPoints) + [0 :: Int ..2] + + let roomWalls = circumference . setFromList @(Set _) <$> roomTiles + allWalls = join roomWalls + + doorPositions <- fmap join . for roomWalls $ \room -> + let candidates = filter (`notMember` circ) room + in fmap toList . choose $ ChooseElement candidates + + let entryways = + filter (\pt -> + let ncs = neighborCells pt + in any ((&&) <$> (not . (wallPositions !)) + <*> (`notMember` villageRegion)) ncs + && any ((&&) <$> (`member` villageRegion) + <*> (`notElem` allWalls)) ncs) + $ toList villageRegion + + Just entryway <- choose $ ChooseElement entryways + + for_ (filter ((&&) <$> (`notElem` doorPositions) <*> (/= entryway)) allWalls) + $ insertEntity Wall + for_ (filter (/= entryway) doorPositions) $ insertEntity unlockedDoor + insertEntity unlockedDoor entryway + + + where + insertEntity e pt = modify $ EntityMap.insertAt (ptToPos pt) $ SomeEntity e + ptToPos pt = _Position # (fromIntegral <$> pt) + + stepOut :: Set (V2 Word) -> [[V2 Word]] -> MaybeT m [[V2 Word]] + stepOut circ rooms = for rooms $ \room -> + let nextLevels = hashNub $ toList . neighborCells =<< room + in pure + . (<> room) + $ filter ((&&) <$> (`notMember` circ) <*> (`notElem` join rooms)) + nextLevels + + circumference pts = + filter (any (`notMember` pts) . neighborCells) $ toList pts + closedHallways = closeHallways livePositions + livePositions = amap not wallPositions + +-------------------------------------------------------------------------------- + +closeHallways :: Cells -> Cells +closeHallways livePositions = + livePositions // mapMaybe closeHallway (assocs livePositions) + where + closeHallway (_, False) = Nothing + closeHallway (pos, _) + | isHallway pos = Just (pos, False) + | otherwise = Nothing + isHallway pos = any ((&&) <$> not . view left <*> not . view right) + . rotations + . fmap (fromMaybe False) + $ arrayNeighbors livePositions pos + +failing :: Monad m => m a -> MaybeT m a -> m a +failing result = (maybe result pure =<<) . runMaybeT + +{- + +import Xanthous.Generators.Village +import Xanthous.Generators +import Xanthous.Data +import System.Random +import qualified Data.Text +import qualified Xanthous.Generators.CaveAutomata as CA +let gi = GeneratorInput SCaveAutomata CA.defaultParams +wallPositions <- generateFromInput gi (Dimensions 80 50) <$> getStdGen +putStrLn . Data.Text.unpack $ showCells wallPositions + +import Data.Array.IArray +let closedHallways = closeHallways . amap not $ wallPositions +putStrLn . Data.Text.unpack . showCells $ amap not closedHallways + +-} diff --git a/users/grfn/xanthous/src/Xanthous/Generators/Speech.hs b/users/grfn/xanthous/src/Xanthous/Generators/Speech.hs new file mode 100644 index 000000000000..8abc00b6a2fc --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Generators/Speech.hs @@ -0,0 +1,181 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE OverloadedLists #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Speech + ( -- * Language definition + Language(..) + -- ** Lenses + , phonotactics + , syllablesPerWord + + -- ** Phonotactics + , Phonotactics(..) + -- *** Lenses + , onsets + , nuclei + , codas + , numOnsets + , numNuclei + , numCodas + + -- * Language generation + , syllable + , word + + -- * Languages + , english + , gormlak + + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (replicateM) +import Data.Interval (Interval, (<=..<=)) +import qualified Data.Interval as Interval +import Control.Monad.Random.Class (MonadRandom) +import Xanthous.Random (chooseRange, choose, ChooseElement (..), Weighted (Weighted)) +import Control.Monad (replicateM) +import Test.QuickCheck (Arbitrary, CoArbitrary, Function) +import Test.QuickCheck.Instances.Text () +import Data.List.NonEmpty (NonEmpty) +-------------------------------------------------------------------------------- + +newtype Phoneme = Phoneme Text + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData, CoArbitrary, Function) + deriving newtype (IsString, Semigroup, Monoid, Arbitrary) + +-- | The phonotactics of a language +-- +-- The phonotactics of a language represent the restriction on the phonemes in +-- the syllables of a language. +-- +-- Syllables in a language consist of an onset, a nucleus, and a coda (the +-- nucleus and the coda together representing the "rhyme" of the syllable). +data Phonotactics = Phonotactics + { _onsets :: [Phoneme] -- ^ The permissible onsets, or consonant clusters + -- at the beginning of a syllable + , _nuclei :: [Phoneme] -- ^ The permissible nuclei, or vowel clusters in + -- the middle of a syllable + , _codas :: [Phoneme] -- ^ The permissible codas, or consonant clusters at + -- the end of a syllable + , _numOnsets :: Interval Word -- ^ The range of number of allowable onsets + , _numNuclei :: Interval Word -- ^ The range of number of allowable nuclei + , _numCodas :: Interval Word -- ^ The range of number of allowable codas + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) +makeLenses ''Phonotactics + +-- | Randomly generate a syllable with the given 'Phonotactics' +syllable :: MonadRandom m => Phonotactics -> m Text +syllable phonotactics = do + let genPart num choices = do + n <- fromIntegral . fromMaybe 0 <$> chooseRange (phonotactics ^. num) + fmap (fromMaybe mempty . mconcat) + . replicateM n + . choose . ChooseElement + $ phonotactics ^. choices + + (Phoneme onset) <- genPart numOnsets onsets + (Phoneme nucleus) <- genPart numNuclei nuclei + (Phoneme coda) <- genPart numCodas codas + + pure $ onset <> nucleus <> coda + +-- | A definition for a language +-- +-- Currently this provides enough information to generate multi-syllabic words, +-- but in the future will likely also include grammar-related things. +data Language = Language + { _phonotactics :: Phonotactics + , _syllablesPerWord :: Weighted Int NonEmpty Int + } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) +makeLenses ''Language + +word :: MonadRandom m => Language -> m Text +word lang = do + numSyllables <- choose $ lang ^. syllablesPerWord + mconcat <$> replicateM numSyllables (syllable $ lang ^. phonotactics) + +-------------------------------------------------------------------------------- + +-- <https://en.wikipedia.org/wiki/English_phonology#Phonotactics> +englishPhonotactics :: Phonotactics +englishPhonotactics = Phonotactics + { _onsets = [ "pl" , "bl" , "kl" , "gl" , "pr" , "br" , "tr" , "dr" , "kr" + , "gr" , "tw" , "dw" , "gw" , "kw" , "pw" + + , "fl" , "sl" , {- "thl", -} "shl" {- , "vl" -} + , "p", "b", "t", "d", "k", "ษก", "m", "n", "f", "v", "th", "s" + , "z", "h", "l", "w" + + , "sp", "st", "sk" + + , "sm", "sn" + + , "sf", "sth" + + , "spl", "skl", "spr", "str", "skr", "skw", "sm", "sp", "st", "sk" + ] + , _nuclei = [ "a", "e", "i", "o", "u", "ur", "ar", "or", "ear", "are", "ure" + , "oa", "ee", "oo", "ei", "ie", "oi", "ou" + ] + , _codas = [ "m", "n", "ng", "p", "t", "tsh", "k", "f", "sh", "s", "th", "x" + , "v", "z", "zh", "l", "r", "w" + + , "lk", "lb", "lt", "ld", "ltsh", "ldsh", "lk" + , "rp", "rb", "rt", "rd", "rtsh", "rdsh", "rk", "rษก" + , "lf", "lv", "lth", "ls", "lz", "lsh", "lth" + , "rf", "rv", "rth", "rs", "rz", "rth" + , "lm", "ln" + , "rm", "rn", "rl" + , "mp", "nt", "nd", "nth", "nsh", "nk" + , "mf", "ms", "mth", "nf", "nth", "ns", "nz", "nth" + , "ft", "sp", "st", "sk" + , "fth" + , "pt", "kt" + , "pth", "ps", "th", "ts", "dth", "dz", "ks" + , "lpt", "lps", "lfth", "lts", "lst", "lkt", "lks" + , "rmth", "rpt", "rps", "rts", "rst", "rkt" + , "mpt", "mps", "ndth", "nkt", "nks", "nkth" + , "ksth", "kst" + ] + , _numOnsets = 0 <=..<= 1 + , _numNuclei = Interval.singleton 1 + , _numCodas = 0 <=..<= 1 + } + +english :: Language +english = Language + { _phonotactics = englishPhonotactics + , _syllablesPerWord = Weighted [(20, 1), + (7, 2), + (2, 3), + (1, 4)] + } + +gormlakPhonotactics :: Phonotactics +gormlakPhonotactics = Phonotactics + { _onsets = [ "h", "l", "g", "b", "m", "n", "ng" + , "gl", "bl", "fl" + ] + , _numOnsets = Interval.singleton 1 + , _nuclei = [ "a", "o", "aa", "u" ] + , _numNuclei = Interval.singleton 1 + , _codas = [ "r", "l", "g", "m", "n" + , "rl", "gl", "ml", "rm" + , "n", "k" + ] + , _numCodas = Interval.singleton 1 + } + +gormlak :: Language +gormlak = Language + { _phonotactics = gormlakPhonotactics + , _syllablesPerWord = Weighted [ (5, 2) + , (5, 1) + , (1, 3) + ] + } diff --git a/users/grfn/xanthous/src/Xanthous/Messages.hs b/users/grfn/xanthous/src/Xanthous/Messages.hs new file mode 100644 index 000000000000..c273d650821b --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Messages.hs @@ -0,0 +1,114 @@ +{-# LANGUAGE TemplateHaskell #-} +-------------------------------------------------------------------------------- +module Xanthous.Messages + ( Message(..) + , resolve + , MessageMap(..) + , lookupMessage + + -- * Game messages + , messages + , render + , render_ + , lookup + , message + , message_ + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (lookup) +-------------------------------------------------------------------------------- +import Control.Monad.Random.Class (MonadRandom) +import Data.Aeson (FromJSON, ToJSON, toJSON, object) +import qualified Data.Aeson as JSON +import Data.Aeson.Generic.DerivingVia +import Data.FileEmbed +import Data.List.NonEmpty +import Test.QuickCheck hiding (choose) +import Test.QuickCheck.Instances.UnorderedContainers () +import Text.Mustache +import qualified Data.Yaml as Yaml +-------------------------------------------------------------------------------- +import Xanthous.Random +import Xanthous.Orphans () +-------------------------------------------------------------------------------- + +data Message = Single Template | Choice (NonEmpty Template) + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (CoArbitrary, Function, NFData) + deriving (ToJSON, FromJSON) + via WithOptions '[ SumEnc UntaggedVal ] + Message + +instance Arbitrary Message where + arbitrary = + frequency [ (10, Single <$> arbitrary) + , (1, Choice <$> arbitrary) + ] + shrink = genericShrink + +resolve :: MonadRandom m => Message -> m Template +resolve (Single t) = pure t +resolve (Choice ts) = choose ts + +data MessageMap = Direct Message | Nested (HashMap Text MessageMap) + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (CoArbitrary, Function, NFData) + deriving (ToJSON, FromJSON) + via WithOptions '[ SumEnc UntaggedVal ] + MessageMap + +instance Arbitrary MessageMap where + arbitrary = frequency [ (10, Direct <$> arbitrary) + , (1, Nested <$> arbitrary) + ] + +lookupMessage :: [Text] -> MessageMap -> Maybe Message +lookupMessage [] (Direct msg) = Just msg +lookupMessage (k:ks) (Nested m) = lookupMessage ks =<< m ^. at k +lookupMessage _ _ = Nothing + +type instance Index MessageMap = [Text] +type instance IxValue MessageMap = Message +instance Ixed MessageMap where + ix [] f (Direct msg) = Direct <$> f msg + ix (k:ks) f (Nested m) = case m ^. at k of + Just m' -> ix ks f m' <&> \m'' -> + Nested $ m & at k ?~ m'' + Nothing -> pure $ Nested m + ix _ _ m = pure m + +-------------------------------------------------------------------------------- + +rawMessages :: ByteString +rawMessages = $(embedFile "src/Xanthous/messages.yaml") + +messages :: MessageMap +messages + = either (error . Yaml.prettyPrintParseException) id + $ Yaml.decodeEither' rawMessages + +render :: (MonadRandom m, ToJSON params) => Message -> params -> m Text +render msg params = do + tpl <- resolve msg + pure . toStrict . renderMustache tpl $ toJSON params + +-- | Render a message with an empty set of params +render_ :: (MonadRandom m) => Message -> m Text +render_ msg = render msg $ object [] + +lookup :: [Text] -> Message +lookup path = fromMaybe notFound $ messages ^? ix path + where notFound + = Single + $ compileMustacheText "template" "Message not found" + ^?! _Right + +message :: (MonadRandom m, ToJSON params) => [Text] -> params -> m Text +message path params = maybe notFound (`render` params) $ messages ^? ix path + where + notFound = pure "Message not found" + +message_ :: (MonadRandom m) => [Text] -> m Text +message_ path = maybe notFound (`render` JSON.object []) $ messages ^? ix path + where + notFound = pure "Message not found" diff --git a/users/grfn/xanthous/src/Xanthous/Messages/Template.hs b/users/grfn/xanthous/src/Xanthous/Messages/Template.hs new file mode 100644 index 000000000000..5176880355f4 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Messages/Template.hs @@ -0,0 +1,275 @@ +{-# LANGUAGE DeriveDataTypeable #-} +-------------------------------------------------------------------------------- +module Xanthous.Messages.Template + ( -- * Template AST + Template(..) + , Substitution(..) + , Filter(..) + + -- ** Template AST transformations + , reduceTemplate + + -- * Template parser + , template + , runParser + , errorBundlePretty + + -- * Template pretty-printer + , ppTemplate + + -- * Rendering templates + , TemplateVar(..) + , nested + , TemplateVars(..) + , vars + , RenderError + , render + ) +where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding + (many, concat, try, elements, some, parts) +-------------------------------------------------------------------------------- +import Test.QuickCheck hiding (label) +import Test.QuickCheck.Instances.Text () +import Test.QuickCheck.Instances.Semigroup () +import Test.QuickCheck.Checkers (EqProp) +import Control.Monad.Combinators.NonEmpty +import Data.List.NonEmpty (NonEmpty(..)) +import Data.Data +import Text.Megaparsec hiding (sepBy1, some) +import Text.Megaparsec.Char +import qualified Text.Megaparsec.Char.Lexer as L +import Data.Function (fix) +-------------------------------------------------------------------------------- +import Xanthous.Util (EqEqProp(..)) +-------------------------------------------------------------------------------- + +genIdentifier :: Gen Text +genIdentifier = pack <$> listOf1 (elements identifierChars) + +identifierChars :: String +identifierChars = ['a'..'z'] <> ['A'..'Z'] <> ['-', '_'] + +newtype Filter = FilterName Text + deriving stock (Show, Eq, Ord, Generic, Data) + deriving anyclass (NFData) + deriving (IsString) via Text + +instance Arbitrary Filter where + arbitrary = FilterName <$> genIdentifier + shrink (FilterName fn) = fmap FilterName . filter (not . null) $ shrink fn + +data Substitution + = SubstPath (NonEmpty Text) + | SubstFilter Substitution Filter + deriving stock (Show, Eq, Ord, Generic, Data) + deriving anyclass (NFData) + +instance Arbitrary Substitution where + arbitrary = sized . fix $ \gen n -> + let leaves = + [ SubstPath <$> ((:|) <$> genIdentifier <*> listOf genIdentifier)] + subtree = gen $ n `div` 2 + in if n == 0 + then oneof leaves + else oneof $ leaves <> [ SubstFilter <$> subtree <*> arbitrary ] + shrink (SubstPath pth) = + fmap SubstPath + . filter (not . any ((||) <$> null <*> any (`notElem` identifierChars))) + $ shrink pth + shrink (SubstFilter s f) + = shrink s + <> (uncurry SubstFilter <$> shrink (s, f)) + +data Template + = Literal Text + | Subst Substitution + | Concat Template Template + deriving stock (Show, Generic, Data) + deriving anyclass (NFData) + deriving EqProp via EqEqProp Template + +instance Plated Template where + plate _ tpl@(Literal _) = pure tpl + plate _ tpl@(Subst _) = pure tpl + plate f (Concat tplโ tplโ) = Concat <$> f tplโ <*> f tplโ + +reduceTemplate :: Template -> Template +reduceTemplate = transform $ \case + (Concat (Literal tโ) (Literal tโ)) -> Literal (tโ <> tโ) + (Concat (Literal "") t) -> t + (Concat t (Literal "")) -> t + (Concat tโ (Concat tโ tโ)) -> Concat (Concat tโ tโ) tโ + (Concat (Concat tโ (Literal tโ)) (Literal tโ)) -> (Concat tโ (Literal $ tโ <> tโ)) + t -> t + +instance Eq Template where + tplโ == tplโ = case (reduceTemplate tplโ, reduceTemplate tplโ) of + (Literal tโ, Literal tโ) -> tโ == tโ + (Subst sโ, Subst sโ) -> sโ == sโ + (Concat taโ taโ, Concat tbโ tbโ) -> taโ == tbโ && taโ == tbโ + _ -> False + +instance Arbitrary Template where + arbitrary = sized . fix $ \gen n -> + let leaves = [ Literal . pack . filter (`notElem` ['\\', '{']) <$> arbitrary + , Subst <$> arbitrary + ] + subtree = gen $ n `div` 2 + genConcat = Concat <$> subtree <*> subtree + in if n == 0 + then oneof leaves + else oneof $ genConcat : leaves + shrink (Literal t) = Literal <$> shrink t + shrink (Subst s) = Subst <$> shrink s + shrink (Concat tโ tโ) + = shrink tโ + <> shrink tโ + <> (Concat <$> shrink tโ <*> shrink tโ) + +instance Semigroup Template where + (<>) = Concat + +instance Monoid Template where + mempty = Literal "" + +-------------------------------------------------------------------------------- + +type Parser = Parsec Void Text + +sc :: Parser () +sc = L.space space1 empty empty + +lexeme :: Parser a -> Parser a +lexeme = L.lexeme sc + +symbol :: Text -> Parser Text +symbol = L.symbol sc + +identifier :: Parser Text +identifier = lexeme . label "identifier" $ do + firstChar <- letterChar <|> oneOf ['-', '_'] + restChars <- many $ alphaNumChar <|> oneOf ['-', '_'] + pure $ firstChar <| pack restChars + +filterName :: Parser Filter +filterName = FilterName <$> identifier + +substitutionPath :: Parser Substitution +substitutionPath = SubstPath <$> sepBy1 identifier (char '.') + +substitutionFilter :: Parser Substitution +substitutionFilter = do + path <- substitutionPath + fs <- some $ symbol "|" *> filterName + pure $ foldl' SubstFilter path fs + -- pure $ SubstFilter path f + +substitutionContents :: Parser Substitution +substitutionContents + = try substitutionFilter + <|> substitutionPath + +substitution :: Parser Substitution +substitution = between (string "{{") (string "}}") substitutionContents + +literal :: Parser Template +literal = Literal <$> + ( (string "\\{" $> "{") + <|> takeWhile1P Nothing (`notElem` ['\\', '{']) + ) + +subst :: Parser Template +subst = Subst <$> substitution + +template' :: Parser Template +template' = do + parts <- many $ literal <|> subst + pure $ foldr Concat (Literal "") parts + + +template :: Parser Template +template = reduceTemplate <$> template' <* eof + +-------------------------------------------------------------------------------- + +ppSubstitution :: Substitution -> Text +ppSubstitution (SubstPath substParts) = intercalate "." substParts +ppSubstitution (SubstFilter s (FilterName f)) = ppSubstitution s <> " | " <> f + +ppTemplate :: Template -> Text +ppTemplate (Literal txt) = txt +ppTemplate (Subst s) = "{{" <> ppSubstitution s <> "}}" +ppTemplate (Concat tplโ tplโ) = ppTemplate tplโ <> ppTemplate tplโ + +-------------------------------------------------------------------------------- + +data TemplateVar + = Val Text + | Nested (Map Text TemplateVar) + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) + +nested :: [(Text, TemplateVar)] -> TemplateVar +nested = Nested . mapFromList + +instance Arbitrary TemplateVar where + arbitrary = sized . fix $ \gen n -> + let nst = fmap mapFromList . listOf $ (,) <$> arbitrary <*> gen (n `div` 2) + in if n == 0 + then Val <$> arbitrary + else oneof [ Val <$> arbitrary + , Nested <$> nst] + +newtype TemplateVars = Vars { getTemplateVars :: Map Text TemplateVar } + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) + deriving (Arbitrary) via (Map Text TemplateVar) + +type instance Index TemplateVars = Text +type instance IxValue TemplateVars = TemplateVar +instance Ixed TemplateVars where + ix k f (Vars vs) = Vars <$> ix k f vs +instance At TemplateVars where + at k f (Vars vs) = Vars <$> at k f vs + +vars :: [(Text, TemplateVar)] -> TemplateVars +vars = Vars . mapFromList + +lookupVar :: TemplateVars -> NonEmpty Text -> Maybe TemplateVar +lookupVar vs (p :| []) = vs ^. at p +lookupVar vs (p :| (pโ : ps)) = vs ^. at p >>= \case + (Val _) -> Nothing + (Nested vs') -> lookupVar (Vars vs') $ pโ :| ps + +data RenderError + = NoSuchVariable (NonEmpty Text) + | NestedFurther (NonEmpty Text) + | NoSuchFilter Filter + deriving stock (Show, Eq, Generic) + deriving anyclass (NFData) + +renderSubst + :: Map Filter (Text -> Text) -- ^ Filters + -> TemplateVars + -> Substitution + -> Either RenderError Text +renderSubst _ vs (SubstPath pth) = + case lookupVar vs pth of + Just (Val v) -> Right v + Just (Nested _) -> Left $ NestedFurther pth + Nothing -> Left $ NoSuchVariable pth +renderSubst fs vs (SubstFilter s fn) = + case fs ^. at fn of + Just filterFn -> filterFn <$> renderSubst fs vs s + Nothing -> Left $ NoSuchFilter fn + +render + :: Map Filter (Text -> Text) -- ^ Filters + -> TemplateVars -- ^ Template variables + -> Template -- ^ Template + -> Either RenderError Text +render _ _ (Literal s) = pure s +render fs vs (Concat tโ tโ) = (<>) <$> render fs vs tโ <*> render fs vs tโ +render fs vs (Subst s) = renderSubst fs vs s diff --git a/users/grfn/xanthous/src/Xanthous/Monad.hs b/users/grfn/xanthous/src/Xanthous/Monad.hs new file mode 100644 index 000000000000..db602de56f3a --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Monad.hs @@ -0,0 +1,76 @@ +-------------------------------------------------------------------------------- +module Xanthous.Monad + ( AppT(..) + , AppM + , runAppT + , continue + , halt + + -- * Messages + , say + , say_ + , message + , message_ + , writeMessage + + -- * Autocommands + , cancelAutocommand + + -- * Events + , sendEvent + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Control.Monad.Random +import Control.Monad.State +import qualified Brick +import Brick (EventM, Next) +import Brick.BChan (writeBChan) +import Data.Aeson (ToJSON, object) +-------------------------------------------------------------------------------- +import Xanthous.Data.App (AppEvent) +import Xanthous.Game.State +import Xanthous.Game.Env +import Xanthous.Messages (Message) +import qualified Xanthous.Messages as Messages +-------------------------------------------------------------------------------- + +halt :: AppT (EventM n) (Next GameState) +halt = lift . Brick.halt =<< get + +continue :: AppT (EventM n) (Next GameState) +continue = lift . Brick.continue =<< get + +-------------------------------------------------------------------------------- + +say :: (MonadRandom m, ToJSON params, MonadState GameState m) + => [Text] -> params -> m () +say msgPath = writeMessage <=< Messages.message msgPath + +say_ :: (MonadRandom m, MonadState GameState m) => [Text] -> m () +say_ msgPath = say msgPath $ object [] + +message :: (MonadRandom m, ToJSON params, MonadState GameState m) + => Message -> params -> m () +message msg = writeMessage <=< Messages.render msg + +message_ :: (MonadRandom m, MonadState GameState m) + => Message -> m () +message_ msg = message msg $ object [] + +writeMessage :: MonadState GameState m => Text -> m () +writeMessage m = messageHistory %= pushMessage m + +-- | Cancel the currently active autocommand, if any +cancelAutocommand :: (MonadState GameState m, MonadIO m) => m () +cancelAutocommand = do + traverse_ (liftIO . cancel . snd) =<< preuse (autocommand . _ActiveAutocommand) + autocommand .= NoAutocommand + +-------------------------------------------------------------------------------- + +-- | Send an event to the app in an environment where the game env is available +sendEvent :: (MonadReader GameEnv m, MonadIO m) => AppEvent -> m () +sendEvent evt = do + ec <- view eventChan + liftIO $ writeBChan ec evt diff --git a/users/grfn/xanthous/src/Xanthous/Orphans.hs b/users/grfn/xanthous/src/Xanthous/Orphans.hs new file mode 100644 index 000000000000..e9cfddc0e69c --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Orphans.hs @@ -0,0 +1,483 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE PackageImports #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# OPTIONS_GHC -Wno-type-defaults #-} +-------------------------------------------------------------------------------- +module Xanthous.Orphans + ( ppTemplate + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (elements, (.=)) +-------------------------------------------------------------------------------- +import Data.Aeson +import Data.Aeson.Types (typeMismatch) +import Data.List.NonEmpty (NonEmpty(..)) +import Graphics.Vty.Attributes +import Brick.Widgets.Edit +import Data.Text.Zipper.Generic (GenericTextZipper) +import Brick.Widgets.Core (getName) +import System.Random.Internal (StdGen (..)) +import System.Random.SplitMix (SMGen ()) +import Test.QuickCheck +import "quickcheck-instances" Test.QuickCheck.Instances () +import Text.Megaparsec (errorBundlePretty) +import Text.Megaparsec.Pos +import Text.Mustache +import Text.Mustache.Type ( showKey ) +import Control.Monad.State +import Linear +import qualified Data.Interval as Interval +import Data.Interval ( Interval, Extended (..), Boundary (..) + , lowerBound', upperBound', (<=..<), (<=..<=) + , interval) +import Test.QuickCheck.Checkers (EqProp ((=-=))) +-------------------------------------------------------------------------------- +import Xanthous.Util.JSON +import Xanthous.Util.QuickCheck +import Xanthous.Util (EqEqProp(EqEqProp)) +-------------------------------------------------------------------------------- + +instance forall s a. + ( Cons s s a a + , IsSequence s + , Element s ~ a + ) => Cons (NonNull s) (NonNull s) a a where + _Cons = prism hither yon + where + hither :: (a, NonNull s) -> NonNull s + hither (a, ns) = + let s = toNullable ns + in impureNonNull $ a <| s + + yon :: NonNull s -> Either (NonNull s) (a, NonNull s) + yon ns = case nuncons ns of + (_, Nothing) -> Left ns + (x, Just xs) -> Right (x, xs) + +instance forall a. Cons (NonEmpty a) (NonEmpty a) a a where + _Cons = prism hither yon + where + hither :: (a, NonEmpty a) -> NonEmpty a + hither (a, x :| xs) = a :| (x : xs) + + yon :: NonEmpty a -> Either (NonEmpty a) (a, NonEmpty a) + yon ns@(x :| xs) = case xs of + (y : ys) -> Right (x, y :| ys) + [] -> Left ns + + +instance Arbitrary PName where + arbitrary = PName . pack <$> listOf1 (elements ['a'..'z']) + +instance Arbitrary Key where + arbitrary = Key <$> listOf1 arbSafeText + where arbSafeText = pack <$> listOf1 (elements ['a'..'z']) + shrink (Key []) = error "unreachable" + shrink k@(Key [_]) = pure k + shrink (Key (p:ps)) = Key . (p :) <$> shrink ps + +instance Arbitrary Pos where + arbitrary = mkPos . succ . abs <$> arbitrary + shrink (unPos -> 1) = [] + shrink (unPos -> x) = mkPos <$> [x..1] + +instance Arbitrary Node where + arbitrary = scale (`div` 10) $ sized node + where + node n | n > 0 = oneof $ leaves ++ branches (n `div` 4) + node _ = oneof leaves + branches n = + [ Section <$> arbitrary <*> subnodes n + , InvertedSection <$> arbitrary <*> subnodes n + ] + subnodes = fmap concatTextBlocks . listOf . node + leaves = + [ TextBlock . pack <$> listOf1 (elements ['a'..'z']) + , EscapedVar <$> arbitrary + , UnescapedVar <$> arbitrary + -- TODO fix pretty-printing of mustache partials + -- , Partial <$> arbitrary <*> arbitrary + ] + shrink = genericShrink + +concatTextBlocks :: [Node] -> [Node] +concatTextBlocks [] = [] +concatTextBlocks [x] = [x] +concatTextBlocks (TextBlock txtโ : TextBlock txtโ : xs) + = concatTextBlocks $ TextBlock (txtโ <> txtโ) : concatTextBlocks xs +concatTextBlocks (x : xs) = x : concatTextBlocks xs + +instance Arbitrary Template where + arbitrary = scale (`div` 8) $ do + template <- concatTextBlocks <$> arbitrary + -- templateName <- arbitrary + -- rest <- arbitrary + let templateName = "template" + rest = mempty + pure $ Template + { templateActual = templateName + , templateCache = rest & at templateName ?~ template + } + shrink (Template actual cache) = + let Just tpl = cache ^. at actual + in do + cache' <- shrink cache + tpl' <- shrink tpl + actual' <- shrink actual + pure $ Template + { templateActual = actual' + , templateCache = cache' & at actual' ?~ tpl' + } + +instance CoArbitrary Template where + coarbitrary = coarbitrary . ppTemplate + +instance Function Template where + function = functionMap ppTemplate parseTemplatePartial + where + parseTemplatePartial txt + = compileMustacheText "template" txt ^?! _Right + +ppNode :: Map PName [Node] -> Node -> Text +ppNode _ (TextBlock txt) = txt +ppNode _ (EscapedVar k) = "{{" <> showKey k <> "}}" +ppNode ctx (Section k body) = + let sk = showKey k + in "{{#" <> sk <> "}}" <> foldMap (ppNode ctx) body <> "{{/" <> sk <> "}}" +ppNode _ (UnescapedVar k) = "{{{" <> showKey k <> "}}}" +ppNode ctx (InvertedSection k body) = + let sk = showKey k + in "{{^" <> sk <> "}}" <> foldMap (ppNode ctx) body <> "{{/" <> sk <> "}}" +ppNode _ (Partial n _) = "{{> " <> unPName n <> "}}" + +ppTemplate :: Template -> Text +ppTemplate (Template actual cache) = + case cache ^. at actual of + Nothing -> error "Template not found?" + Just nodes -> foldMap (ppNode cache) nodes + +instance ToJSON Template where + toJSON = String . ppTemplate + +instance FromJSON Template where + parseJSON + = withText "Template" + $ either (fail . errorBundlePretty) pure + . compileMustacheText "template" + +deriving anyclass instance NFData Node +deriving anyclass instance NFData Template + +instance FromJSON Color where + parseJSON (String "black") = pure black + parseJSON (String "red") = pure red + parseJSON (String "green") = pure green + parseJSON (String "yellow") = pure yellow + parseJSON (String "blue") = pure blue + parseJSON (String "magenta") = pure magenta + parseJSON (String "cyan") = pure cyan + parseJSON (String "white") = pure white + parseJSON (String "brightBlack") = pure brightBlack + parseJSON (String "brightRed") = pure brightRed + parseJSON (String "brightGreen") = pure brightGreen + parseJSON (String "brightYellow") = pure brightYellow + parseJSON (String "brightBlue") = pure brightBlue + parseJSON (String "brightMagenta") = pure brightMagenta + parseJSON (String "brightCyan") = pure brightCyan + parseJSON (String "brightWhite") = pure brightWhite + parseJSON n@(Number _) = Color240 <$> parseJSON n + parseJSON x = typeMismatch "Color" x + +instance ToJSON Color where + toJSON color + | color == black = "black" + | color == red = "red" + | color == green = "green" + | color == yellow = "yellow" + | color == blue = "blue" + | color == magenta = "magenta" + | color == cyan = "cyan" + | color == white = "white" + | color == brightBlack = "brightBlack" + | color == brightRed = "brightRed" + | color == brightGreen = "brightGreen" + | color == brightYellow = "brightYellow" + | color == brightBlue = "brightBlue" + | color == brightMagenta = "brightMagenta" + | color == brightCyan = "brightCyan" + | color == brightWhite = "brightWhite" + | Color240 num <- color = toJSON num + | otherwise = error $ "unimplemented: " <> show color + +instance (Eq a, Show a, Read a, FromJSON a) => FromJSON (MaybeDefault a) where + parseJSON Null = pure Default + parseJSON (String "keepCurrent") = pure KeepCurrent + parseJSON x = SetTo <$> parseJSON x + +instance ToJSON a => ToJSON (MaybeDefault a) where + toJSON Default = Null + toJSON KeepCurrent = String "keepCurrent" + toJSON (SetTo x) = toJSON x + +-------------------------------------------------------------------------------- + +instance Arbitrary Color where + arbitrary = oneof [ Color240 <$> choose (0, 239) + , ISOColor <$> choose (0, 15) + ] + +deriving anyclass instance CoArbitrary Color +deriving anyclass instance Function Color + +instance (Eq a, Show a, Read a, Arbitrary a) => Arbitrary (MaybeDefault a) where + arbitrary = oneof [ pure Default + , pure KeepCurrent + , SetTo <$> arbitrary + ] + +instance CoArbitrary a => CoArbitrary (MaybeDefault a) where + coarbitrary Default = variant @Int 1 + coarbitrary KeepCurrent = variant @Int 2 + coarbitrary (SetTo x) = variant @Int 3 . coarbitrary x + +instance (Eq a, Show a, Read a, Function a) => Function (MaybeDefault a) where + function = functionShow + +deriving via (EqEqProp Attr) instance EqProp Attr + +instance Arbitrary Attr where + arbitrary = do + attrStyle <- arbitrary + attrForeColor <- arbitrary + attrBackColor <- arbitrary + attrURL <- arbitrary + pure Attr {..} + +deriving anyclass instance CoArbitrary Attr +deriving anyclass instance Function Attr + +instance ToJSON Attr where + toJSON Attr{..} = object + [ "style" .= maybeDefaultToJSONWith styleToJSON attrStyle + , "foreground" .= attrForeColor + , "background" .= attrBackColor + , "url" .= attrURL + ] + where + maybeDefaultToJSONWith _ Default = Null + maybeDefaultToJSONWith _ KeepCurrent = String "keepCurrent" + maybeDefaultToJSONWith tj (SetTo x) = tj x + styleToJSON style + | style == standout = "standout" + | style == underline = "underline" + | style == reverseVideo = "reverseVideo" + | style == blink = "blink" + | style == dim = "dim" + | style == bold = "bold" + | style == italic = "italic" + | otherwise = toJSON style + +instance FromJSON Attr where + parseJSON = withObject "Attr" $ \obj -> do + attrStyle <- parseStyle =<< obj .:? "style" .!= Default + attrForeColor <- obj .:? "foreground" .!= Default + attrBackColor <- obj .:? "background" .!= Default + attrURL <- obj .:? "url" .!= Default + pure Attr{..} + + where + parseStyle (SetTo (String "standout")) = pure (SetTo standout) + parseStyle (SetTo (String "underline")) = pure (SetTo underline) + parseStyle (SetTo (String "reverseVideo")) = pure (SetTo reverseVideo) + parseStyle (SetTo (String "blink")) = pure (SetTo blink) + parseStyle (SetTo (String "dim")) = pure (SetTo dim) + parseStyle (SetTo (String "bold")) = pure (SetTo bold) + parseStyle (SetTo (String "italic")) = pure (SetTo italic) + parseStyle (SetTo n@(Number _)) = SetTo <$> parseJSON n + parseStyle (SetTo v) = typeMismatch "Style" v + parseStyle Default = pure Default + parseStyle KeepCurrent = pure KeepCurrent + +deriving stock instance Ord Color +deriving stock instance Ord a => Ord (MaybeDefault a) +deriving stock instance Ord Attr + +-------------------------------------------------------------------------------- + +instance (SemiSequence a, Arbitrary (Element a), Arbitrary a) + => Arbitrary (NonNull a) where + arbitrary = ncons <$> arbitrary <*> arbitrary + +instance ToJSON a => ToJSON (NonNull a) where + toJSON = toJSON . toNullable + +instance (FromJSON a, MonoFoldable a) => FromJSON (NonNull a) where + parseJSON = maybe (fail "Found empty list") pure . fromNullable <=< parseJSON + +instance NFData a => NFData (NonNull a) where + rnf xs = xs `seq` toNullable xs `deepseq` () + +-------------------------------------------------------------------------------- + +instance forall t name. (NFData t, Monoid t, NFData name) + => NFData (Editor t name) where + rnf ed = getName @_ @name ed `deepseq` getEditContents ed `deepseq` () + +deriving via (ReadShowJSON SMGen) instance ToJSON SMGen +deriving via (ReadShowJSON SMGen) instance FromJSON SMGen + +instance ToJSON StdGen where + toJSON = toJSON . unStdGen + toEncoding = toEncoding . unStdGen + +instance FromJSON StdGen where + parseJSON = fmap StdGen . parseJSON + +-------------------------------------------------------------------------------- + +instance CoArbitrary a => CoArbitrary (NonNull a) where + coarbitrary = coarbitrary . toNullable + +instance (MonoFoldable a, Function a) => Function (NonNull a) where + function = functionMap toNullable $ fromMaybe (error "null") . fromNullable + +instance (Arbitrary t, Arbitrary n, GenericTextZipper t) + => Arbitrary (Editor t n) where + arbitrary = editor <$> arbitrary <*> arbitrary <*> arbitrary + +instance forall t n. (CoArbitrary t, CoArbitrary n, Monoid t) + => CoArbitrary (Editor t n) where + coarbitrary ed = coarbitrary (getName @_ @n ed, getEditContents ed) + +instance CoArbitrary StdGen where + coarbitrary = coarbitrary . show + +instance Function StdGen where + function = functionMap unStdGen StdGen + +instance Function SMGen where + function = functionShow + +-------------------------------------------------------------------------------- + +deriving newtype instance (Arbitrary s, CoArbitrary (m (a, s))) + => CoArbitrary (StateT s m a) + +-------------------------------------------------------------------------------- + +deriving via (GenericArbitrary (V2 a)) instance Arbitrary a => Arbitrary (V2 a) +instance CoArbitrary a => CoArbitrary (V2 a) +instance Function a => Function (V2 a) + +-------------------------------------------------------------------------------- + +instance CoArbitrary Boundary +instance Function Boundary + +instance Arbitrary a => Arbitrary (Extended a) where + arbitrary = oneof [ pure NegInf + , pure PosInf + , Finite <$> arbitrary + ] + +instance CoArbitrary a => CoArbitrary (Extended a) where + coarbitrary NegInf = variant 1 + coarbitrary PosInf = variant 2 + coarbitrary (Finite x) = variant 3 . coarbitrary x + +instance (Function a) => Function (Extended a) where + function = functionMap g h + where + g NegInf = Left True + g (Finite a) = Right a + g PosInf = Left False + h (Left False) = PosInf + h (Left True) = NegInf + h (Right a) = Finite a + +instance ToJSON a => ToJSON (Extended a) where + toJSON NegInf = String "NegInf" + toJSON PosInf = String "PosInf" + toJSON (Finite x) = toJSON x + +instance FromJSON a => FromJSON (Extended a) where + parseJSON (String "NegInf") = pure NegInf + parseJSON (String "PosInf") = pure PosInf + parseJSON val = Finite <$> parseJSON val + +instance (EqProp a, Show a) => EqProp (Extended a) where + NegInf =-= NegInf = property True + PosInf =-= PosInf = property True + (Finite x) =-= (Finite y) = x =-= y + x =-= y = counterexample (show x <> " /= " <> show y) False + +instance Arbitrary Interval.Boundary where + arbitrary = elements [ Interval.Open , Interval.Closed ] + +instance (Ord r, Arbitrary r) => Arbitrary (Interval r) where + arbitrary = do + lower <- arbitrary + upper <- arbitrary + pure $ (if upper < lower then flip else id) + Interval.interval + lower + upper + +instance CoArbitrary a => CoArbitrary (Interval a) where + coarbitrary int = coarbitrary (lowerBound' int) . coarbitrary (upperBound' int) + +instance (Function a, Ord a) => Function (Interval a) where + function = functionMap g h + where + g = lowerBound' &&& upperBound' + h = uncurry interval + +deriving via (EqEqProp (Interval a)) instance Eq a => (EqProp (Interval a)) + +instance ToJSON a => ToJSON (Interval a) where + toJSON x = Array . fromList $ + [ object [ lowerKey .= lowerVal ] + , object [ upperKey .= upperVal ] + ] + where + (lowerVal, lowerBoundary) = lowerBound' x + (upperVal, upperBoundary) = upperBound' x + upperKey = boundaryToKey upperBoundary + lowerKey = boundaryToKey lowerBoundary + boundaryToKey Open = "Excluded" + boundaryToKey Closed = "Included" + +instance forall a. (FromJSON a, Ord a) => FromJSON (Interval a) where + parseJSON x = + boundPairWithBoundary x + <|> boundPairWithoutBoundary x + <|> singleVal x + where + boundPairWithBoundary = withArray "Bound pair" $ \arr -> do + checkLength arr + lower <- parseBound $ arr ^?! ix 0 + upper <- parseBound $ arr ^?! ix 1 + pure $ interval lower upper + parseBound = withObject "Bound" $ \obj -> do + when (length obj /= 1) $ fail "Expected an object with a single key" + let [(k, v)] = obj ^@.. ifolded + boundary <- case k of + "Excluded" -> pure Open + "Open" -> pure Open + "Included" -> pure Closed + "Closed" -> pure Closed + _ -> fail "Invalid boundary specification" + val <- parseJSON v + pure (val, boundary) + boundPairWithoutBoundary = withArray "Bound pair" $ \arr -> do + checkLength arr + lower <- parseJSON $ arr ^?! ix 0 + upper <- parseJSON $ arr ^?! ix 1 + pure $ lower <=..< upper + singleVal v = do + val <- parseJSON v + pure $ val <=..<= val + checkLength arr = + when (length arr /= 2) $ fail "Expected array of length 2" diff --git a/users/grfn/xanthous/src/Xanthous/Physics.hs b/users/grfn/xanthous/src/Xanthous/Physics.hs new file mode 100644 index 000000000000..37530cbbc21b --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Physics.hs @@ -0,0 +1,71 @@ +-------------------------------------------------------------------------------- +module Xanthous.Physics + ( throwDistance + , bluntThrowDamage + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Xanthous.Data + ( Meters + , (:**:)(..) + , Square + , Grams + , (|*|) + , (|/|) + , Hitpoints + , Per (..) + , squared + , Uno(..), (|+|) + ) +-------------------------------------------------------------------------------- + +-- university shotputter can put a 16 lb shot about 14 meters +-- โ 7.25 kg 14 meters +-- 14m = x / (7.25kg ร y + z)ยฒ +-- 14m = x / (7250g ร y + z)ยฒ +-- +-- we don't want to scale down too much: +-- +-- 10 kg 10 meters +-- = 10000 g 10 meters +-- +-- 15 kg w meters +-- = 15000 g w meters +-- +-- 14m = x / (7250g ร y + z)ยฒ +-- 10m = x / (10000g ร y + z)ยฒ +-- wm = x / (15000g ร y + z)ยฒ +-- +-- wโ0.527301 โง yโ0.000212178 sqrt(x) โง zโ1.80555 sqrt(x) โง 22824.1 sqrt(x)!=0 +-- +-- x = 101500 +-- y = 0.0675979 +-- z = 575.231 +-- + +-- TODO make this dynamic +strength :: Meters :**: Square Grams +strength = Times 10150000 + +yCoeff :: Uno Double +yCoeff = Uno 0.0675979 + +zCoeff :: Uno Double +zCoeff = Uno 575.231 + +-- | Calculate the maximum distance an object with the given weight can be +-- thrown +throwDistance + :: Grams -- ^ Weight of the object + -> Meters -- ^ Max distance thrown +throwDistance weight = strength |/| squared (weight |*| yCoeff |+| zCoeff) + +-- | Returns the damage dealt by a blunt object with the given weight when +-- thrown +bluntThrowDamage + :: Grams + -> Hitpoints +bluntThrowDamage weight = throwDamageRatio |*| weight + where + throwDamageRatio :: Hitpoints `Per` Grams + throwDamageRatio = Rate $ 1 / 5000 diff --git a/users/grfn/xanthous/src/Xanthous/Prelude.hs b/users/grfn/xanthous/src/Xanthous/Prelude.hs new file mode 100644 index 000000000000..2cb4299303ba --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Prelude.hs @@ -0,0 +1,48 @@ +-------------------------------------------------------------------------------- +module Xanthous.Prelude + ( module ClassyPrelude + , Type + , Constraint + , module GHC.TypeLits + , module Control.Lens + , module Data.Void + , module Control.Comonad + , module Witherable + , fail + + , (&!) + + -- * Classy-Prelude addons + , ninsertSet + , ndeleteSet + , toVector + ) where +-------------------------------------------------------------------------------- +import ClassyPrelude hiding + ( return, (<|), unsnoc, uncons, cons, snoc, index, (<.>), Index, say + , catMaybes, filter, mapMaybe, hashNub, ordNub + , Memoized, runMemoized + ) +import Data.Kind +import GHC.TypeLits hiding (Text) +import Control.Lens hiding (levels, Level) +import Data.Void +import Control.Comonad +import Witherable +import Control.Monad.Fail (fail) +-------------------------------------------------------------------------------- + +ninsertSet + :: (IsSet set, MonoPointed set) + => Element set -> NonNull set -> NonNull set +ninsertSet x xs = impureNonNull $ opoint x `union` toNullable xs + +ndeleteSet :: IsSet b => Element b -> NonNull b -> b +ndeleteSet x = deleteSet x . toNullable + +toVector :: (MonoFoldable (f a), Element (f a) ~ a) => f a -> Vector a +toVector = fromList . toList + +infixl 1 &! +(&!) :: a -> (a -> b) -> b +(&!) = flip ($!) diff --git a/users/grfn/xanthous/src/Xanthous/Random.hs b/users/grfn/xanthous/src/Xanthous/Random.hs new file mode 100644 index 000000000000..329b321b8bda --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Random.hs @@ -0,0 +1,186 @@ +-------------------------------------------------------------------------------- +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +-------------------------------------------------------------------------------- +module Xanthous.Random + ( Choose(..) + , ChooseElement(..) + , Weighted(..) + , evenlyWeighted + , weightedBy + , subRand + , chance + , chooseSubset + , chooseRange + , FiniteInterval(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.List.NonEmpty (NonEmpty(..)) +import Control.Monad.Random.Class (MonadRandom(getRandomR, getRandom)) +import Control.Monad.Random (Rand, evalRand, mkStdGen, StdGen) +import Data.Functor.Compose +import Data.Random.Shuffle.Weighted +import Data.Random.Distribution +import Data.Random.Distribution.Uniform +import Data.Random.Distribution.Uniform.Exclusive +import Data.Random.Sample +import qualified Data.Random.Source as DRS +import Data.Interval ( Interval, lowerBound', Extended (Finite) + , upperBound', Boundary (Closed), lowerBound, upperBound + ) +-------------------------------------------------------------------------------- + +instance {-# INCOHERENT #-} (Monad m, MonadRandom m) => DRS.MonadRandom m where + getRandomWord8 = getRandom + getRandomWord16 = getRandom + getRandomWord32 = getRandom + getRandomWord64 = getRandom + getRandomDouble = getRandom + getRandomNByteInteger n = getRandomR (0, 256 ^ n) + +class Choose a where + type RandomResult a + choose :: MonadRandom m => a -> m (RandomResult a) + +newtype ChooseElement a = ChooseElement a + +instance MonoFoldable a => Choose (ChooseElement a) where + type RandomResult (ChooseElement a) = Maybe (Element a) + choose (ChooseElement xs) = do + chosenIdx <- getRandomR (0, olength xs - 1) + let pick _ (Just x) = Just x + pick (x, i) Nothing + | i == chosenIdx = Just x + | otherwise = Nothing + pure $ ofoldr pick Nothing $ zip (toList xs) [0..] + +instance MonoFoldable a => Choose (NonNull a) where + type RandomResult (NonNull a) = Element a + choose + = fmap (fromMaybe (error "unreachable")) -- why not lol + . choose + . ChooseElement + . toNullable + +instance Choose (NonEmpty a) where + type RandomResult (NonEmpty a) = a + choose = choose . fromNonEmpty @[_] + +instance Choose (a, a) where + type RandomResult (a, a) = a + choose (x, y) = choose (x :| [y]) + +newtype Weighted w t a = Weighted (t (w, a)) + deriving (Functor, Foldable) via (t `Compose` (,) w) + +deriving newtype instance Eq (t (w, a)) => Eq (Weighted w t a) +deriving newtype instance Show (t (w, a)) => Show (Weighted w t a) +deriving newtype instance NFData (t (w, a)) => NFData (Weighted w t a) + +instance Traversable t => Traversable (Weighted w t) where + traverse f (Weighted twa) = Weighted <$> (traverse . traverse) f twa + +evenlyWeighted :: [a] -> Weighted Int [] a +evenlyWeighted = Weighted . itoList + +-- | Weight the elements of some functor by a function. Larger values of 'w' per +-- its 'Ord' instance will be more likely to be generated +weightedBy :: Functor t => (a -> w) -> t a -> Weighted w t a +weightedBy weighting xs = Weighted $ (weighting &&& id) <$> xs + +instance (Num w, Ord w, Distribution Uniform w, Excludable w) + => Choose (Weighted w [] a) where + type RandomResult (Weighted w [] a) = Maybe a + choose (Weighted ws) = sample $ headMay <$> weightedSample 1 ws + +instance (Num w, Ord w, Distribution Uniform w, Excludable w) + => Choose (Weighted w NonEmpty a) where + type RandomResult (Weighted w NonEmpty a) = a + choose (Weighted ws) = + sample + $ fromMaybe (error "unreachable") . headMay + <$> weightedSample 1 (toList ws) + +subRand :: MonadRandom m => Rand StdGen a -> m a +subRand sub = evalRand sub . mkStdGen <$> getRandom + +-- | Has a @n@ chance of returning 'True' +-- +-- eg, chance 0.5 will return 'True' half the time +chance + :: (Num w, Ord w, Distribution Uniform w, Excludable w, MonadRandom m) + => w + -> m Bool +chance n = choose $ weightedBy (bool 1 (n * 2)) bools + +-- | Choose a random subset of *about* @w@ of the elements of the given +-- 'Witherable' structure +chooseSubset :: ( Num w, Ord w, Distribution Uniform w, Excludable w + , Witherable t + , MonadRandom m + ) => w -> t a -> m (t a) +chooseSubset = filterA . const . chance + +-- | Choose a random @n@ in the given interval +chooseRange + :: ( MonadRandom m + , Distribution Uniform n + , Enum n + , Bounded n + , Ord n + ) + => Interval n + -> m (Maybe n) +chooseRange int = traverse sample distribution + where + (lower, lowerBoundary) = lowerBound' int + lowerR = case lower of + Finite x -> if lowerBoundary == Closed + then x + else succ x + _ -> minBound + (upper, upperBoundary) = upperBound' int + upperR = case upper of + Finite x -> if upperBoundary == Closed + then x + else pred x + _ -> maxBound + distribution + | lowerR <= upperR = Just $ Uniform lowerR upperR + | otherwise = Nothing + +instance ( Distribution Uniform n + , Enum n + , Bounded n + , Ord n + ) + => Choose (Interval n) where + type RandomResult (Interval n) = n + choose = fmap (fromMaybe $ error "Invalid interval") . chooseRange + +newtype FiniteInterval a + = FiniteInterval { unwrapFiniteInterval :: (Interval a) } + +instance ( Distribution Uniform n + , Ord n + ) + => Choose (FiniteInterval n) where + type RandomResult (FiniteInterval n) = n + -- TODO broken with open/closed right now + choose + = sample + . uncurry Uniform + . over both getFinite + . (lowerBound &&& upperBound) + . unwrapFiniteInterval + where + getFinite (Finite x) = x + getFinite _ = error "Infinite value" + +-------------------------------------------------------------------------------- + +bools :: NonEmpty Bool +bools = True :| [False] diff --git a/users/grfn/xanthous/src/Xanthous/Util.hs b/users/grfn/xanthous/src/Xanthous/Util.hs new file mode 100644 index 000000000000..f918340f055b --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util.hs @@ -0,0 +1,351 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE QuantifiedConstraints #-} +-------------------------------------------------------------------------------- +module Xanthous.Util + ( EqEqProp(..) + , EqProp(..) + , foldlMapM + , foldlMapM' + , between + + , appendVia + + -- * Foldable + -- ** Uniqueness + -- *** Predicates on uniqueness + , isUniqueOf + , isUnique + -- *** Removing all duplicate elements in n * log n time + , uniqueOf + , unique + -- *** Removing sequentially duplicate elements in linear time + , uniqOf + , uniq + -- ** Bag sequence algorithms + , takeWhileInclusive + , smallestNotIn + , removeVectorIndex + , removeFirst + , maximum1 + , minimum1 + + -- * Combinators + , times, times_, endoTimes + + -- * State utilities + , modifyK, modifyKL, useListOf + + -- * Type-level programming utils + , KnownBool(..) + + -- * + , AlphaChar(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (foldr) +-------------------------------------------------------------------------------- +import Test.QuickCheck.Checkers +import Data.Foldable (foldr) +import Data.Monoid +import Data.Proxy +import qualified Data.Vector as V +import Data.Semigroup (Max(..), Min(..)) +import Data.Semigroup.Foldable +import Control.Monad.State.Class +import Control.Monad.State (evalState) +-------------------------------------------------------------------------------- + +newtype EqEqProp a = EqEqProp a + deriving newtype Eq + +instance Eq a => EqProp (EqEqProp a) where + (=-=) = eq + +foldlMapM :: forall g b a m. (Foldable g, Monoid b, Applicative m) => (a -> m b) -> g a -> m b +foldlMapM f = foldr f' (pure mempty) + where + f' :: a -> m b -> m b + f' x = liftA2 mappend (f x) + +-- Strict in the monoidal accumulator. For monads strict +-- in the left argument of bind, this will run in constant +-- space. +foldlMapM' :: forall g b a m. (Foldable g, Monoid b, Monad m) => (a -> m b) -> g a -> m b +foldlMapM' f xs = foldr f' pure xs mempty + where + f' :: a -> (b -> m b) -> b -> m b + f' x k bl = do + br <- f x + let !b = mappend bl br + k b + +-- | Returns whether the third argument is in the range given by the first two +-- arguments, inclusive +-- +-- >>> between (0 :: Int) 2 2 +-- True +-- +-- >>> between (0 :: Int) 2 3 +-- False +between + :: Ord a + => a -- ^ lower bound + -> a -- ^ upper bound + -> a -- ^ scrutinee + -> Bool +between lower upper x = x >= lower && x <= upper + +-- | +-- >>> appendVia Sum 1 2 +-- 3 +appendVia :: (Rewrapping s t, Semigroup s) => (Unwrapped s -> s) -> Unwrapped s -> Unwrapped s -> Unwrapped s +appendVia wrap x y = op wrap $ wrap x <> wrap y + +-------------------------------------------------------------------------------- + +-- | Returns True if the targets of the given 'Fold' are unique per the 'Ord' instance for @a@ +-- +-- >>> isUniqueOf (folded . _1) ([(1, 2), (2, 2), (3, 2)] :: [(Int, Int)]) +-- True +-- +-- >>> isUniqueOf (folded . _2) ([(1, 2), (2, 2), (3, 2)] :: [(Int, Int)]) +-- False +-- +-- @ +-- 'isUniqueOf' :: Ord a => 'Getter' s a -> s -> 'Bool' +-- 'isUniqueOf' :: Ord a => 'Fold' s a -> s -> 'Bool' +-- 'isUniqueOf' :: Ord a => 'Lens'' s a -> s -> 'Bool' +-- 'isUniqueOf' :: Ord a => 'Iso'' s a -> s -> 'Bool' +-- 'isUniqueOf' :: Ord a => 'Traversal'' s a -> s -> 'Bool' +-- 'isUniqueOf' :: Ord a => 'Prism'' s a -> s -> 'Bool' +-- @ +isUniqueOf :: Ord a => Getting (Endo (Set a, Bool)) s a -> s -> Bool +isUniqueOf aFold = orOf _2 . foldrOf aFold rejectUnique (mempty, True) + where + rejectUnique x (seen, acc) + | seen ^. contains x = (seen, False) + | otherwise = (seen & contains x .~ True, acc) + +-- | Returns true if the given 'Foldable' container contains only unique +-- elements, as determined by the 'Ord' instance for @a@ +-- +-- >>> isUnique ([3, 1, 2] :: [Int]) +-- True +-- +-- >>> isUnique ([1, 1, 2, 2, 3, 1] :: [Int]) +-- False +isUnique :: (Foldable f, Ord a) => f a -> Bool +isUnique = isUniqueOf folded + + +-- | O(n * log n). Returns a monoidal, 'Cons'able container (a list, a Set, +-- etc.) consisting of the unique (per the 'Ord' instance for @a@) targets of +-- the given 'Fold' +-- +-- >>> uniqueOf (folded . _2) ([(1, 2), (2, 2), (3, 2), (4, 3)] :: [(Int, Int)]) :: [Int] +-- [2,3] +-- +-- @ +-- 'uniqueOf' :: Ord a => 'Getter' s a -> s -> [a] +-- 'uniqueOf' :: Ord a => 'Fold' s a -> s -> [a] +-- 'uniqueOf' :: Ord a => 'Lens'' s a -> s -> [a] +-- 'uniqueOf' :: Ord a => 'Iso'' s a -> s -> [a] +-- 'uniqueOf' :: Ord a => 'Traversal'' s a -> s -> [a] +-- 'uniqueOf' :: Ord a => 'Prism'' s a -> s -> [a] +-- @ +uniqueOf + :: (Monoid c, Ord w, Cons c c w w) => Getting (Endo (Set w, c)) a w -> a -> c +uniqueOf aFold = snd . foldrOf aFold rejectUnique (mempty, mempty) + where + rejectUnique x (seen, acc) + | seen ^. contains x = (seen, acc) + | otherwise = (seen & contains x .~ True, cons x acc) + +-- | Returns a monoidal, 'Cons'able container (a list, a Set, etc.) consisting +-- of the unique (per the 'Ord' instance for @a@) contents of the given +-- 'Foldable' container +-- +-- >>> unique [1, 1, 2, 2, 3, 1] :: [Int] +-- [2,3,1] + +-- >>> unique [1, 1, 2, 2, 3, 1] :: Set Int +-- fromList [3,2,1] +unique :: (Foldable f, Cons c c a a, Ord a, Monoid c) => f a -> c +unique = uniqueOf folded + +-------------------------------------------------------------------------------- + +-- | O(n). Returns a monoidal, 'Cons'able container (a list, a Vector, etc.) +-- consisting of the targets of the given 'Fold' with sequential duplicate +-- elements removed +-- +-- This function (sorry for the confusing name) differs from 'uniqueOf' in that +-- it only compares /sequentially/ duplicate elements (and thus operates in +-- linear time). +-- cf 'Data.Vector.uniq' and POSIX @uniq@ for the name +-- +-- >>> uniqOf (folded . _2) ([(1, 2), (2, 2), (3, 1), (4, 2)] :: [(Int, Int)]) :: [Int] +-- [2,1,2] +-- +-- @ +-- 'uniqOf' :: Eq a => 'Getter' s a -> s -> [a] +-- 'uniqOf' :: Eq a => 'Fold' s a -> s -> [a] +-- 'uniqOf' :: Eq a => 'Lens'' s a -> s -> [a] +-- 'uniqOf' :: Eq a => 'Iso'' s a -> s -> [a] +-- 'uniqOf' :: Eq a => 'Traversal'' s a -> s -> [a] +-- 'uniqOf' :: Eq a => 'Prism'' s a -> s -> [a] +-- @ +uniqOf :: (Monoid c, Cons c c w w, Eq w) => Getting (Endo (Maybe w, c)) a w -> a -> c +uniqOf aFold = snd . foldrOf aFold rejectSeen (Nothing, mempty) + where + rejectSeen x (Nothing, acc) = (Just x, x <| acc) + rejectSeen x tup@(Just a, acc) + | x == a = tup + | otherwise = (Just x, x <| acc) + +-- | O(n). Returns a monoidal, 'Cons'able container (a list, a Vector, etc.) +-- consisting of the targets of the given 'Foldable' container with sequential +-- duplicate elements removed +-- +-- This function (sorry for the confusing name) differs from 'unique' in that +-- it only compares /sequentially/ unique elements (and thus operates in linear +-- time). +-- cf 'Data.Vector.uniq' and POSIX @uniq@ for the name +-- +-- >>> uniq [1, 1, 1, 2, 2, 2, 3, 3, 1] :: [Int] +-- [1,2,3,1] +-- +-- >>> uniq [1, 1, 1, 2, 2, 2, 3, 3, 1] :: Vector Int +-- [1,2,3,1] +-- +uniq :: (Foldable f, Eq a, Cons c c a a, Monoid c) => f a -> c +uniq = uniqOf folded + +-- | Like 'takeWhile', but inclusive +takeWhileInclusive :: (a -> Bool) -> [a] -> [a] +takeWhileInclusive _ [] = [] +takeWhileInclusive p (x:xs) = x : if p x then takeWhileInclusive p xs else [] + +-- | Returns the smallest value not in a list +smallestNotIn :: (Ord a, Bounded a, Enum a) => [a] -> a +smallestNotIn xs = case uniq $ sort xs of + [] -> minBound + xs'@(x : _) + | x > minBound -> minBound + | otherwise + -> snd . headEx . filter (uncurry (/=)) $ zip (xs' ++ [minBound]) [minBound..] + +-- | Remove the element at the given index, if any, from the given vector +removeVectorIndex :: Int -> Vector a -> Vector a +removeVectorIndex idx vect = + let (before, after) = V.splitAt idx vect + in before <> fromMaybe Empty (tailMay after) + +-- | Remove the first element in a sequence that matches a given predicate +removeFirst :: IsSequence seq => (Element seq -> Bool) -> seq -> seq +removeFirst p + = flip evalState False + . filterM (\x -> do + found <- get + let matches = p x + when matches $ put True + pure $ found || not matches) + +maximum1 :: (Ord a, Foldable1 f) => f a -> a +maximum1 = getMax . foldMap1 Max + +minimum1 :: (Ord a, Foldable1 f) => f a -> a +minimum1 = getMin . foldMap1 Min + +times :: (Applicative f, Num n, Enum n) => n -> (n -> f b) -> f [b] +times n f = traverse f [1..n] + +times_ :: (Applicative f, Num n, Enum n) => n -> f a -> f [a] +times_ n fa = times n (const fa) + +-- | Multiply an endomorphism by an integral +-- +-- >>> endoTimes (4 :: Int) succ (5 :: Int) +-- 9 +endoTimes :: Integral n => n -> (a -> a) -> a -> a +endoTimes n f = appEndo $ stimes n (Endo f) + +-------------------------------------------------------------------------------- + +-- | This class gives a boolean associated with a type-level bool, a'la +-- 'KnownSymbol', 'KnownNat' etc. +class KnownBool (bool :: Bool) where + boolVal' :: forall proxy. proxy bool -> Bool + boolVal' _ = boolVal @bool + + boolVal :: Bool + boolVal = boolVal' $ Proxy @bool + +instance KnownBool 'True where boolVal = True +instance KnownBool 'False where boolVal = False + +-------------------------------------------------------------------------------- + +-- | Modify some monadic state via the application of a kleisli endomorphism on +-- the state itself +-- +-- Note that any changes made to the state during execution of @k@ will be +-- overwritten +-- +-- @@ +-- modifyK pure === pure () +-- @@ +modifyK :: MonadState s m => (s -> m s) -> m () +modifyK k = get >>= k >>= put + +-- | Modify some monadic state via the application of a kleisli endomorphism on +-- the target of a lens +-- +-- Note that any changes made to the state during execution of @k@ will be +-- overwritten +-- +-- @@ +-- modifyKL id pure === pure () +-- @@ +modifyKL :: MonadState s m => LensLike m s s a b -> (a -> m b) -> m () +modifyKL l k = get >>= traverseOf l k >>= put + +-- | Use a list of all the targets of a 'Fold' in the current state +-- +-- @@ +-- evalState (useListOf folded) === toList +-- @@ +useListOf :: MonadState s m => Getting (Endo [a]) s a -> m [a] +useListOf = gets . toListOf + +-------------------------------------------------------------------------------- + +-- | A newtype wrapper around 'Char' whose 'Enum' and 'Bounded' instances only +-- include the characters @[a-zA-Z]@ +-- +-- >>> succ (AlphaChar 'z') +-- 'A' +newtype AlphaChar = AlphaChar { getAlphaChar :: Char } + deriving stock Show + deriving (Eq, Ord) via Char + +instance Enum AlphaChar where + toEnum n + | between 0 25 n + = AlphaChar . toEnum $ n + fromEnum 'a' + | between 26 51 n + = AlphaChar . toEnum $ n - 26 + fromEnum 'A' + | otherwise + = error $ "Tag " <> show n <> " out of range [0, 51] for enum AlphaChar" + fromEnum (AlphaChar chr) + | between 'a' 'z' chr + = fromEnum chr - fromEnum 'a' + | between 'A' 'Z' chr + = fromEnum chr - fromEnum 'A' + | otherwise + = error $ "Invalid value for alpha char: " <> show chr + +instance Bounded AlphaChar where + minBound = AlphaChar 'a' + maxBound = AlphaChar 'Z' diff --git a/users/grfn/xanthous/src/Xanthous/Util/Comonad.hs b/users/grfn/xanthous/src/Xanthous/Util/Comonad.hs new file mode 100644 index 000000000000..9e158cc8e2d4 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/Comonad.hs @@ -0,0 +1,24 @@ +-------------------------------------------------------------------------------- +module Xanthous.Util.Comonad + ( -- * Store comonad utils + replace + , current + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Control.Comonad.Store.Class +-------------------------------------------------------------------------------- + +-- | Replace the current position of a store comonad with a new value by +-- comparing positions +replace :: (Eq i, ComonadStore i w) => w a -> a -> w a +replace w x = w =>> \w' -> if pos w' == pos w then x else extract w' +{-# INLINE replace #-} + +-- | Lens into the current position of a store comonad. +-- +-- current = lens extract replace +current :: (Eq i, ComonadStore i w) => Lens' (w a) a +current = lens extract replace +{-# INLINE current #-} diff --git a/users/grfn/xanthous/src/Xanthous/Util/Graph.hs b/users/grfn/xanthous/src/Xanthous/Util/Graph.hs new file mode 100644 index 000000000000..8e5c04f4bfa9 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/Graph.hs @@ -0,0 +1,33 @@ +-------------------------------------------------------------------------------- +module Xanthous.Util.Graph where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Graph.Inductive.Query.MST (msTree) +import qualified Data.Graph.Inductive.Graph as Graph +import Data.Graph.Inductive.Graph +import Data.Graph.Inductive.Basic (undir) +import Data.Set (isSubsetOf) +-------------------------------------------------------------------------------- + +mstSubGraph + :: forall gr node edge. (DynGraph gr, Real edge, Show edge) + => gr node edge -> gr node edge +mstSubGraph graph = insEdges mstEdges . insNodes (labNodes graph) $ Graph.empty + where + mstEdges = ordNub $ do + LP path <- msTree $ undir graph + case path of + [] -> [] + [_] -> [] + ((nโ, edgeWeight) : (nโ, _) : _) -> + pure (nโ, nโ, edgeWeight) + +isSubGraphOf + :: (Graph gr1, Graph gr2, Ord node, Ord edge) + => gr1 node edge + -> gr2 node edge + -> Bool +isSubGraphOf graphโ graphโ + = setFromList (labNodes graphโ) `isSubsetOf` setFromList (labNodes graphโ) + && setFromList (labEdges graphโ) `isSubsetOf` setFromList (labEdges graphโ) diff --git a/users/grfn/xanthous/src/Xanthous/Util/Graphics.hs b/users/grfn/xanthous/src/Xanthous/Util/Graphics.hs new file mode 100644 index 000000000000..0cb009f45ad0 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/Graphics.hs @@ -0,0 +1,177 @@ +{-# LANGUAGE TemplateHaskell #-} +-- | Graphics algorithms and utils for rendering things in 2D space +-------------------------------------------------------------------------------- +module Xanthous.Util.Graphics + ( circle + , filledCircle + , line + , straightLine + , delaunay + + -- * Debugging and testing tools + , renderBooleanGraphics + , showBooleanGraphics + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +-- https://github.com/noinia/hgeometry/issues/28 +-- import qualified Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer +-- as Geometry +import qualified Algorithms.Geometry.DelaunayTriangulation.Naive + as Geometry +import qualified Algorithms.Geometry.DelaunayTriangulation.Types as Geometry +import Control.Monad.State (execState, State) +import qualified Data.Geometry.Point as Geometry +import Data.Ext ((:+)(..)) +import Data.List (unfoldr) +import Data.List.NonEmpty (NonEmpty((:|))) +import qualified Data.List.NonEmpty as NE +import Data.Ix (Ix) +import Linear.V2 +-------------------------------------------------------------------------------- + + +-- | Generate a circle centered at the given point and with the given radius +-- using the <midpoint circle algorithm +-- https://en.wikipedia.org/wiki/Midpoint_circle_algorithm>. +-- +-- Code taken from <https://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#Haskell> +circle :: (Num i, Ord i) + => V2 i -- ^ center + -> i -- ^ radius + -> [V2 i] +circle (V2 xโ yโ) radius + -- Four initial points, plus the generated points + = V2 xโ (yโ + radius) + : V2 xโ (yโ - radius) + : V2 (xโ + radius) yโ + : V2 (xโ - radius) yโ + : points + where + -- Creates the (x, y) octet offsets, then maps them to absolute points in all octets. + points = concatMap generatePoints $ unfoldr step initialValues + + generatePoints (V2 x y) + = [ V2 (xโ `xop` x') (yโ `yop` y') + | (x', y') <- [(x, y), (y, x)] + , xop <- [(+), (-)] + , yop <- [(+), (-)] + ] + + initialValues = (1 - radius, 1, (-2) * radius, 0, radius) + + step (f, ddf_x, ddf_y, x, y) + | x >= y = Nothing + | otherwise = Just (V2 x' y', (f', ddf_x', ddf_y', x', y')) + where + (f', ddf_y', y') | f >= 0 = (f + ddf_y' + ddf_x', ddf_y + 2, y - 1) + | otherwise = (f + ddf_x, ddf_y, y) + ddf_x' = ddf_x + 2 + x' = x + 1 + + +data FillState i + = FillState + { _inCircle :: Bool + , _result :: NonEmpty (V2 i) + } +makeLenses ''FillState + +runFillState :: NonEmpty (V2 i) -> State (FillState i) a -> [V2 i] +runFillState circumference s + = toList + . view result + . execState s + $ FillState False circumference + +-- | Generate a *filled* circle centered at the given point and with the given +-- radius by filling a circle generated with 'circle' +filledCircle :: (Num i, Integral i, Ix i) + => V2 i -- ^ center + -> i -- ^ radius + -> [V2 i] +filledCircle center radius = + case NE.nonEmpty (circle center radius) of + Nothing -> [] + Just circumference -> runFillState circumference $ + -- the first and last lines of all circles are solid, so the whole "in the + -- circle, out of the circle" thing doesn't work... but that's fine since + -- we don't need to fill them. So just skip them + for_ [succ minX..pred maxX] $ \x -> + for_ [minY..maxY] $ \y -> do + let pt = V2 x y + next = V2 x $ succ y + whenM (use inCircle) $ result %= NE.cons pt + + when (pt `elem` circumference && next `notElem` circumference) + $ inCircle %= not + + where + (V2 minX minY, V2 maxX maxY) = minmaxes circumference + +-- | Draw a line between two points using Bresenham's line drawing algorithm +-- +-- Code taken from <https://wiki.haskell.org/Bresenham%27s_line_drawing_algorithm> +line :: (Num i, Ord i) => V2 i -> V2 i -> [V2 i] +line pa@(V2 xa ya) pb@(V2 xb yb) + = (if maySwitch pa < maySwitch pb then id else reverse) points + where + points = map maySwitch . unfoldr go $ (xโ, yโ, 0) + steep = abs (yb - ya) > abs (xb - xa) + maySwitch = if steep then view _yx else id + [V2 xโ yโ, V2 xโ yโ] = sort [maySwitch pa, maySwitch pb] + ฮดx = xโ - xโ + ฮดy = abs (yโ - yโ) + ystep = if yโ < yโ then 1 else -1 + go (xTemp, yTemp, err) + | xTemp > xโ = Nothing + | otherwise = Just (V2 xTemp yTemp, (xTemp + 1, newY, newError)) + where + tempError = err + ฮดy + (newY, newError) = if (2 * tempError) >= ฮดx + then (yTemp + ystep, tempError - ฮดx) + else (yTemp, tempError) +{-# SPECIALIZE line :: V2 Int -> V2 Int -> [V2 Int] #-} +{-# SPECIALIZE line :: V2 Word -> V2 Word -> [V2 Word] #-} + +straightLine :: (Num i, Ord i) => V2 i -> V2 i -> [V2 i] +straightLine pa@(V2 xa _) pb@(V2 _ yb) = line pa midpoint ++ line midpoint pb + where midpoint = V2 xa yb + +delaunay + :: (Ord n, Fractional n) + => NonEmpty (V2 n, p) + -> [((V2 n, p), (V2 n, p))] +delaunay + = map (over both fromPoint) + . Geometry.edgesAsPoints + . Geometry.delaunayTriangulation + . map toPoint + where + toPoint (V2 px py, pid) = Geometry.Point2 px py :+ pid + fromPoint (Geometry.Point2 px py :+ pid) = (V2 px py, pid) + +-------------------------------------------------------------------------------- + +renderBooleanGraphics :: forall i. (Num i, Ord i, Enum i) => [V2 i] -> String +renderBooleanGraphics [] = "" +renderBooleanGraphics (pt : pts') = intercalate "\n" rows + where + rows = row <$> [minX..maxX] + row x = [minY..maxY] <&> \y -> if V2 x y `member` ptSet then 'X' else ' ' + (V2 minX minY, V2 maxX maxY) = minmaxes pts + pts = pt :| pts' + ptSet :: Set (V2 i) + ptSet = setFromList $ toList pts + +showBooleanGraphics :: forall i. (Num i, Ord i, Enum i) => [V2 i] -> IO () +showBooleanGraphics = putStrLn . pack . renderBooleanGraphics + +minmaxes :: forall i. (Ord i) => NonEmpty (V2 i) -> (V2 i, V2 i) +minmaxes xs = + ( V2 (minimum1Of (traverse1 . _x) xs) + (minimum1Of (traverse1 . _y) xs) + , V2 (maximum1Of (traverse1 . _x) xs) + (maximum1Of (traverse1 . _y) xs) + ) diff --git a/users/grfn/xanthous/src/Xanthous/Util/Inflection.hs b/users/grfn/xanthous/src/Xanthous/Util/Inflection.hs new file mode 100644 index 000000000000..724f2339dd21 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/Inflection.hs @@ -0,0 +1,14 @@ + +module Xanthous.Util.Inflection + ( toSentence + ) where + +import Xanthous.Prelude + +toSentence :: (MonoFoldable mono, Element mono ~ Text) => mono -> Text +toSentence xs = case reverse . toList $ xs of + [] -> "" + [x] -> x + [b, a] -> a <> " and " <> b + (final : butlast) -> + intercalate ", " (reverse butlast) <> ", and " <> final diff --git a/users/grfn/xanthous/src/Xanthous/Util/JSON.hs b/users/grfn/xanthous/src/Xanthous/Util/JSON.hs new file mode 100644 index 000000000000..91d1328e4a10 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/JSON.hs @@ -0,0 +1,19 @@ +-------------------------------------------------------------------------------- +module Xanthous.Util.JSON + ( ReadShowJSON(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import Data.Aeson +-------------------------------------------------------------------------------- + +newtype ReadShowJSON a = ReadShowJSON a + deriving newtype (Read, Show) + +instance Show a => ToJSON (ReadShowJSON a) where + toJSON = toJSON . show + +instance Read a => FromJSON (ReadShowJSON a) where + parseJSON = withText "readable" + $ maybe (fail "Could not read") pure . readMay diff --git a/users/grfn/xanthous/src/Xanthous/Util/Optparse.hs b/users/grfn/xanthous/src/Xanthous/Util/Optparse.hs new file mode 100644 index 000000000000..dfa65372351d --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/Optparse.hs @@ -0,0 +1,21 @@ +-------------------------------------------------------------------------------- +module Xanthous.Util.Optparse + ( readWithGuard + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +-------------------------------------------------------------------------------- +import qualified Options.Applicative as Opt +-------------------------------------------------------------------------------- + +readWithGuard + :: Read b + => (b -> Bool) + -> (b -> String) + -> Opt.ReadM b +readWithGuard predicate errmsg = do + res <- Opt.auto + unless (predicate res) + $ Opt.readerError + $ errmsg res + pure res diff --git a/users/grfn/xanthous/src/Xanthous/Util/QuickCheck.hs b/users/grfn/xanthous/src/Xanthous/Util/QuickCheck.hs new file mode 100644 index 000000000000..be12bc294513 --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/Util/QuickCheck.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE UndecidableInstances #-} +module Xanthous.Util.QuickCheck + ( functionShow + , FunctionShow(..) + , functionJSON + , FunctionJSON(..) + , genericArbitrary + , GenericArbitrary(..) + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude +import Test.QuickCheck +import Test.QuickCheck.Function +import Test.QuickCheck.Instances.ByteString () +import Test.QuickCheck.Arbitrary.Generic +import Data.Aeson +import GHC.Generics (Rep) +-------------------------------------------------------------------------------- + +newtype FunctionShow a = FunctionShow a + deriving newtype (Show, Read) + +instance (Show a, Read a) => Function (FunctionShow a) where + function = functionShow + +functionJSON :: (ToJSON a, FromJSON a) => (a -> c) -> a :-> c +functionJSON = functionMap encode (headEx . decode) + +newtype FunctionJSON a = FunctionJSON a + deriving newtype (ToJSON, FromJSON) + +instance (ToJSON a, FromJSON a) => Function (FunctionJSON a) where + function = functionJSON + +-------------------------------------------------------------------------------- + +newtype GenericArbitrary a = GenericArbitrary a + deriving newtype Generic + +instance (Generic a, GArbitrary rep, Rep a ~ rep) + => Arbitrary (GenericArbitrary a) where + arbitrary = genericArbitrary diff --git a/users/grfn/xanthous/src/Xanthous/messages.yaml b/users/grfn/xanthous/src/Xanthous/messages.yaml new file mode 100644 index 000000000000..27ee841dd9db --- /dev/null +++ b/users/grfn/xanthous/src/Xanthous/messages.yaml @@ -0,0 +1,161 @@ +welcome: Welcome to Xanthous, {{characterName}}! It's dangerous out there, why not stay inside? Use hjklybnu to move. +dead: + - You have died... + - You die... + - You perish... + - You have perished... + +generic: + continue: Press enter to continue... + +save: + disabled: "Sorry, saving is currently disabled" + location: "Enter filename to save to: " + overwrite: "A file named {{filename}} already exists. Would you like to overwrite it? " + +quit: + confirm: Really quit without saving? + +entities: + description: You see here {{entityDescriptions}} + say: + creature: + visible: The {{creature.creatureType.name}} {{creature.creatureType.sayVerb}} "{{message}}" + invisible: You hear something yell "{{message}}" in the distance + +pickUp: + menu: What would you like to pick up? + pickUp: You pick up the {{item.itemType.name}}. + nothingToPickUp: "There's nothing here to pick up" + +cant: + goUp: + - You can't go up here + - There's nothing here that would let you go up + goDown: + - You can't go down here + - There's nothing here that would let you go down + +open: + prompt: Direction to open (hjklybnu.)? + success: "You open the door." + locked: "That door is locked" + nothingToOpen: "There's nothing to open there." + alreadyOpen: "That door is already open." + +close: + prompt: Direction to close (hjklybnu.)? + success: + - You close the door. + - You shut the door. + nothingToClose: "There's nothing to close there." + alreadyClosed: "That door is already closed." + blocked: "The {{entityDescriptions}} {{blockOrBlocks}} the door!" + +look: + prompt: Select a position on the map to describe (use Enter to confirm) + nothing: There's nothing there + +character: + namePrompt: "What's your name? " + body: + knuckles: + calluses: + - You've started developing calluses on your knuckles from all the punching you've been doing. + - You've been fighting with your fists so much they're starting to develop calluses. + +combat: + nothingToAttack: There's nothing to attack there. + menu: Which creature would you like to attack? + fistSelfDamage: + - You hit so hard with your fists you hurt yourself! + - The punch leaves your knuckles bloody! + fistExtraSelfDamage: + - You hurt your already-bloody fists with the strike! + - Ouch! Your fists were already bleeding! + hit: + fists: + - You punch the {{creature.creatureType.name}} with your bare fists! It hurts. A lot. + - You strike the {{creature.creatureType.name}} with your bare fists! It leaves a bit of a bruise on your knuckles. + generic: + - You hit the {{creature.creatureType.name}}. + - You attack the {{creature.creatureType.name}}. + creatureAttack: + natural: The {{creature.creatureType.name}} {{attackDescription}}. + genericWeapon: The {{creature.creatureType.name}} attacks you with its {{item.itemType.name}}. + killed: + - You kill the {{creature.creatureType.name}}! + - You've killed the {{creature.creatureType.name}}! + +debug: + toggleRevealAll: revealAll now set to {{revealAll}} + +eat: + noFood: + - You have nothing edible. + - You don't have any food. + - You don't have anything to eat. + - You search your pockets for something edible, and come up short. + menuPrompt: What would you like to eat? + eat: You eat the {{item.itemType.name}}. + +read: + prompt: Direction to read (hjklybnu.)? + nothing: "There's nothing there to read" + result: "\"{{message}}\"" + +inventory: + describe: + select: Select an item in your inventory to describe + nothing: You aren't carrying anything + +wield: + nothing: + - You aren't carrying anything you can wield + - You can't wield anything in your backpack + - You can't wield anything currently in your backpack + menu: What would you like to wield? + # TODO: use actual hands + wielded : You wield the {{wieldedItem.itemType.name}} in your right hand. + +fire: + nothing: + - You don't currently have anything you can throw + - You don't have anything to throw + zeroRange: + - That item is too heavy to throw! + - That's too heavy to throw + - You're not strong enough to throw that any meaningful distance + menu: What would you like to throw? + target: Choose a target + atRange: + - It's too heavy for you to throw any further than this + fired: + noTarget: + - You throw the {{item.itemType.name}} at the ground + noDamage: + - You throw the {{item.itemType.name}} at the {{creature.creatureType.name}}. It doesn't seem to care. + - You throw the {{item.itemType.name}} at the {{creature.creatureType.name}}. It doesn't seem to do anything. + - You throw the {{item.itemType.name}} at the {{creature.creatureType.name}}. It doesn't seem to hurt it. + someDamage: + - You throw the {{item.itemType.name}} at the {{creature.creatureType.name}}. It hits it on the head!. + +drop: + nothing: You aren't carrying anything + menu: What would you like to drop? + # TODO: use actual hands + dropped: + - You drop the {{item.itemType.name}}. + - You drop the {{item.itemType.name}} on the ground. + - You put the {{item.itemType.name}} on the ground. + - You take the {{item.itemType.name}} out of your backpack and put it on the ground. + - You take the {{item.itemType.name}} out of your backpack and drop it on the ground. + +autocommands: + enemyInSight: There's a {{firstEntity.creatureType.name}} nearby! + resting: Resting... + doneResting: Done resting +### + +tutorial: + message1: The caves are dark and full of nightmarish creatures - and you are likely to perish without food. Seek out sustenance! You can pick items up with ,. diff --git a/users/grfn/xanthous/test/Spec.hs b/users/grfn/xanthous/test/Spec.hs new file mode 100644 index 000000000000..64c10cf21e20 --- /dev/null +++ b/users/grfn/xanthous/test/Spec.hs @@ -0,0 +1,59 @@ +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import qualified Xanthous.Data.EntitiesSpec +import qualified Xanthous.Data.EntityCharSpec +import qualified Xanthous.Data.EntityMap.GraphicsSpec +import qualified Xanthous.Data.EntityMapSpec +import qualified Xanthous.Data.LevelsSpec +import qualified Xanthous.Data.MemoSpec +import qualified Xanthous.Data.NestedMapSpec +import qualified Xanthous.DataSpec +import qualified Xanthous.Entities.CommonSpec +import qualified Xanthous.Entities.RawsSpec +import qualified Xanthous.Entities.RawTypesSpec +import qualified Xanthous.Entities.CharacterSpec +import qualified Xanthous.GameSpec +import qualified Xanthous.Game.StateSpec +import qualified Xanthous.Game.PromptSpec +import qualified Xanthous.Generators.Level.UtilSpec +import qualified Xanthous.MessageSpec +import qualified Xanthous.Messages.TemplateSpec +import qualified Xanthous.OrphansSpec +import qualified Xanthous.RandomSpec +import qualified Xanthous.Util.GraphSpec +import qualified Xanthous.Util.GraphicsSpec +import qualified Xanthous.Util.InflectionSpec +import qualified Xanthous.UtilSpec +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMainWithRerun test + +test :: TestTree +test = testGroup "Xanthous" + [ Xanthous.Data.EntitiesSpec.test + , Xanthous.Data.EntityMap.GraphicsSpec.test + , Xanthous.Data.EntityMapSpec.test + , Xanthous.Data.LevelsSpec.test + , Xanthous.Data.MemoSpec.test + , Xanthous.Data.NestedMapSpec.test + , Xanthous.DataSpec.test + , Xanthous.Entities.CommonSpec.test + , Xanthous.Entities.RawsSpec.test + , Xanthous.Entities.CharacterSpec.test + , Xanthous.Entities.RawTypesSpec.test + , Xanthous.GameSpec.test + , Xanthous.Game.StateSpec.test + , Xanthous.Game.PromptSpec.test + , Xanthous.Generators.Level.UtilSpec.test + , Xanthous.MessageSpec.test + , Xanthous.Messages.TemplateSpec.test + , Xanthous.OrphansSpec.test + , Xanthous.RandomSpec.test + , Xanthous.Util.GraphSpec.test + , Xanthous.Util.GraphicsSpec.test + , Xanthous.Util.InflectionSpec.test + , Xanthous.UtilSpec.test + , Xanthous.Data.EntityCharSpec.test + ] diff --git a/users/grfn/xanthous/test/Test/Prelude.hs b/users/grfn/xanthous/test/Test/Prelude.hs new file mode 100644 index 000000000000..75c1ebf5e76a --- /dev/null +++ b/users/grfn/xanthous/test/Test/Prelude.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +-------------------------------------------------------------------------------- +module Test.Prelude + ( module Xanthous.Prelude + , module Test.Tasty + , module Test.Tasty.HUnit + , module Test.Tasty.QuickCheck + , module Test.Tasty.Ingredients.Rerun + , module Test.QuickCheck.Classes + , testBatch + , jsonRoundTrip + ) where +-------------------------------------------------------------------------------- +import Xanthous.Prelude hiding (assert, elements) +-------------------------------------------------------------------------------- +import Test.Tasty +import Test.Tasty.QuickCheck +import Test.Tasty.HUnit +import Test.Tasty.Ingredients.Rerun +import Test.QuickCheck.Classes +import Test.QuickCheck.Checkers (TestBatch, EqProp ((=-=))) +import Test.QuickCheck.Instances.ByteString () +-------------------------------------------------------------------------------- +import qualified Data.Aeson as JSON +import Data.Aeson (ToJSON, FromJSON) +-------------------------------------------------------------------------------- + +testBatch :: TestBatch -> TestTree +testBatch (name, tests) = testGroup name $ uncurry testProperty <$> tests + +jsonRoundTrip + :: forall a. (ToJSON a, FromJSON a, EqProp a, Arbitrary a, Show a) => TestTree +jsonRoundTrip = testProperty "JSON round trip" $ \(x :: a) -> + JSON.decode (JSON.encode x) =-= Just x diff --git a/users/grfn/xanthous/test/Xanthous/Data/EntitiesSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/EntitiesSpec.hs new file mode 100644 index 000000000000..e403503743c0 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/EntitiesSpec.hs @@ -0,0 +1,28 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.EntitiesSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import qualified Data.Aeson as JSON +-------------------------------------------------------------------------------- +import Xanthous.Data.Entities +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.Entities" + [ testGroup "Collision" + [ testProperty "JSON round-trip" $ \(c :: Collision) -> + JSON.decode (JSON.encode c) === Just c + , testGroup "JSON encoding examples" + [ testCase "Stop" $ JSON.encode Stop @?= "\"Stop\"" + , testCase "Combat" $ JSON.encode Combat @?= "\"Combat\"" + ] + ] + , testGroup "EntityAttributes" + [ testProperty "JSON round-trip" $ \(ea :: EntityAttributes) -> + JSON.decode (JSON.encode ea) === Just ea + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Data/EntityCharSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/EntityCharSpec.hs new file mode 100644 index 000000000000..9e8024c9d223 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/EntityCharSpec.hs @@ -0,0 +1,18 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityCharSpec where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import qualified Data.Aeson as JSON +-------------------------------------------------------------------------------- +import Xanthous.Data.EntityChar +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.EntityChar" + [ testProperty "JSON round-trip" $ \(ec :: EntityChar) -> + JSON.decode (JSON.encode ec) === Just ec + ] diff --git a/users/grfn/xanthous/test/Xanthous/Data/EntityMap/GraphicsSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/EntityMap/GraphicsSpec.hs new file mode 100644 index 000000000000..fd37548ce864 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/EntityMap/GraphicsSpec.hs @@ -0,0 +1,57 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityMap.GraphicsSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +import Data.Aeson +-------------------------------------------------------------------------------- +import Xanthous.Game.State +import Xanthous.Data +import Xanthous.Data.EntityMap +import Xanthous.Data.EntityMap.Graphics +import Xanthous.Entities.Environment (Wall(..)) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.EntityMap.Graphics" + [ testGroup "visiblePositions" + [ testProperty "one step in each cardinal direction is always visible" + $ \pos (Cardinal dir) (Positive r) (wallPositions :: Set Position)-> + pos `notMember` wallPositions ==> + let em = review _EntityMap . map (, Wall) . toList $ wallPositions + em' = em & atPosition (move dir pos) %~ (Wall <|) + poss = visiblePositions pos r em' + in counterexample ("visiblePositions: " <> show poss) + $ move dir pos `member` poss + , testGroup "bugs" + [ testCase "non-contiguous bug 1" + $ let charPos = Position 20 20 + gormlakPos = Position 17 19 + em = insertAt gormlakPos TestEntity + . insertAt charPos TestEntity + $ mempty + visPositions = visiblePositions charPos 12 em + in (gormlakPos `member` visPositions) @? + ( "not (" + <> show gormlakPos <> " `member` " + <> show visPositions + <> ")" + ) + ] + ] + ] + +-------------------------------------------------------------------------------- + +data TestEntity = TestEntity + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (ToJSON, FromJSON, NFData) + +instance Brain TestEntity where + step _ = pure +instance Draw TestEntity +instance Entity TestEntity where + description _ = "" + entityChar _ = "e" diff --git a/users/grfn/xanthous/test/Xanthous/Data/EntityMapSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/EntityMapSpec.hs new file mode 100644 index 000000000000..7c5cad019616 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/EntityMapSpec.hs @@ -0,0 +1,69 @@ +{-# LANGUAGE ApplicativeDo #-} +-------------------------------------------------------------------------------- +module Xanthous.Data.EntityMapSpec where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import qualified Data.Aeson as JSON +-------------------------------------------------------------------------------- +import Xanthous.Data.EntityMap +import Xanthous.Data (Positioned(..)) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = localOption (QuickCheckTests 20) + $ testGroup "Xanthous.Data.EntityMap" + [ testBatch $ monoid @(EntityMap Int) mempty + , testGroup "Deduplicate" + [ testGroup "Semigroup laws" + [ testProperty "associative" $ \(a :: Deduplicate (EntityMap Int)) b c -> + a <> (b <> c) === (a <> b) <> c + ] + ] + , testGroup "Eq laws" + [ testProperty "reflexivity" $ \(em :: EntityMap Int) -> + em == em + , testProperty "symmetric" $ \(emโ :: EntityMap Int) emโ -> + (emโ == emโ) == (emโ == emโ) + , testProperty "transitive" $ \(emโ :: EntityMap Int) emโ emโ -> + if (emโ == emโ && emโ == emโ) + then (emโ == emโ) + else True + ] + , testGroup "JSON encoding/decoding" + [ testProperty "round-trips" $ \(em :: EntityMap Int) -> + let em' = JSON.decode (JSON.encode em) + in counterexample (show (em' ^? _Just . lastID, em ^. lastID + , em' ^? _Just . byID == em ^. byID . re _Just + , em' ^? _Just . byPosition == em ^. byPosition . re _Just + , em' ^? _Just . _EntityMap == em ^. _EntityMap . re _Just + )) + $ em' === Just em + , testProperty "Preserves IDs" $ \(em :: EntityMap Int) -> + let Just em' = JSON.decode $ JSON.encode em + in toEIDsAndPositioned em' === toEIDsAndPositioned em + ] + + , localOption (QuickCheckTests 50) + $ testGroup "atPosition" + [ testProperty "setget" $ \pos (em :: EntityMap Int) es -> + view (atPosition pos) (set (atPosition pos) es em) === es + , testProperty "getset" $ \pos (em :: EntityMap Int) -> + set (atPosition pos) (view (atPosition pos) em) em === em + , testProperty "setset" $ \pos (em :: EntityMap Int) es -> + (set (atPosition pos) es . set (atPosition pos) es) em + === + set (atPosition pos) es em + -- testProperty "lens laws" $ \pos -> isLens $ atPosition @Int pos + , testProperty "preserves IDs" $ \(em :: EntityMap Int) e1 e2 p -> + let (eid, em') = insertAtReturningID p e1 em + em'' = em' & atPosition p %~ (e2 <|) + in + counterexample ("em': " <> show em') + . counterexample ("em'': " <> show em'') + $ em'' ^. at eid === Just (Positioned p e1) + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Data/LevelsSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/LevelsSpec.hs new file mode 100644 index 000000000000..a7528331627d --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/LevelsSpec.hs @@ -0,0 +1,66 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.LevelsSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import qualified Data.Aeson as JSON +-------------------------------------------------------------------------------- +import Xanthous.Util (between) +import Xanthous.Data.Levels +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.Levels" + [ testGroup "current" + [ testProperty "view is extract" $ \(levels :: Levels Int) -> + levels ^. current === extract levels + , testProperty "set replaces current" $ \(levels :: Levels Int) new -> + extract (set current new levels) === new + , testProperty "set extract is id" $ \(levels :: Levels Int) -> + set current (extract levels) levels === levels + , testProperty "set y โ set x โก set y" $ \(levels :: Levels Int) x y -> + set current y (set current x levels) === set current y levels + ] + , localOption (QuickCheckTests 20) + $ testBatch $ semigroup @(Levels Int) (error "unused", 1 :: Int) + , testGroup "next/prev" + [ testGroup "nextLevel" + [ testProperty "seeks forwards" $ \(levels :: Levels Int) genned -> + (pos . runIdentity . nextLevel (Identity genned) $ levels) + === pos levels + 1 + , testProperty "maintains the invariant" $ \(levels :: Levels Int) genned -> + let levels' = runIdentity . nextLevel (Identity genned) $ levels + in between 0 (toEnum $ length levels') $ pos levels' + , testProperty "extract is total" $ \(levels :: Levels Int) genned -> + let levels' = runIdentity . nextLevel (Identity genned) $ levels + in total $ extract levels' + , testProperty "uses the generated level as the next level" + $ \(levels :: Levels Int) genned -> + let levels' = seek (toEnum $ length levels - 1) levels + levels'' = runIdentity . nextLevel (Identity genned) $ levels' + in counterexample (show levels'') + $ extract levels'' === genned + ] + , testGroup "prevLevel" + [ testProperty "seeks backwards" $ \(levels :: Levels Int) -> + case prevLevel levels of + Nothing -> property Discard + Just levels' -> pos levels' === pos levels - 1 + , testProperty "maintains the invariant" $ \(levels :: Levels Int) -> + case prevLevel levels of + Nothing -> property Discard + Just levels' -> property $ between 0 (toEnum $ length levels') $ pos levels' + , testProperty "extract is total" $ \(levels :: Levels Int) -> + case prevLevel levels of + Nothing -> property Discard + Just levels' -> total $ extract levels' + ] + ] + , testGroup "JSON" + [ testProperty "toJSON/parseJSON round-trip" $ \(levels :: Levels Int) -> + JSON.decode (JSON.encode levels) === Just levels + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Data/MemoSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/MemoSpec.hs new file mode 100644 index 000000000000..ad81f1984d8f --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/MemoSpec.hs @@ -0,0 +1,19 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.MemoSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +import Test.QuickCheck.Instances.Text () +-------------------------------------------------------------------------------- +import Xanthous.Data.Memo +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.MemoSpec" + [ testGroup "getMemoized" + [ testProperty "when key matches" $ \k v -> + getMemoized @Int @Int k (memoizeWith k v) === Just v + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Data/NestedMapSpec.hs b/users/grfn/xanthous/test/Xanthous/Data/NestedMapSpec.hs new file mode 100644 index 000000000000..acf7a67268f4 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Data/NestedMapSpec.hs @@ -0,0 +1,20 @@ +-------------------------------------------------------------------------------- +module Xanthous.Data.NestedMapSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Test.QuickCheck.Instances.Semigroup () +-------------------------------------------------------------------------------- +import qualified Xanthous.Data.NestedMap as NM +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data.NestedMap" + [ testProperty "insert/lookup" $ \nm ks v -> + let nm' = NM.insert ks v nm + in counterexample ("inserted: " <> show nm') + $ NM.lookup @Map @Int @Int ks nm' === Just (NM.Val v) + ] diff --git a/users/grfn/xanthous/test/Xanthous/DataSpec.hs b/users/grfn/xanthous/test/Xanthous/DataSpec.hs new file mode 100644 index 000000000000..9e67505ba928 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/DataSpec.hs @@ -0,0 +1,109 @@ +-------------------------------------------------------------------------------- +module Xanthous.DataSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude hiding (Right, Left, Down, toList, all) +import Data.Group +import Data.Foldable (toList, all) +-------------------------------------------------------------------------------- +import Xanthous.Data +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Data" + [ testGroup "Position" + [ testBatch $ monoid @Position mempty + , testProperty "group laws" $ \(pos :: Position) -> + pos <> invert pos == mempty && invert pos <> pos == mempty + , testGroup "stepTowards laws" + [ testProperty "takes only one step" $ \src tgt -> + src /= tgt ==> + isUnit (src `diffPositions` (src `stepTowards` tgt)) + -- , testProperty "moves in the right direction" $ \src tgt -> + -- stepTowards src tgt == move (directionOf src tgt) src + ] + , testProperty "directionOf laws" $ \pos dir -> + directionOf pos (move dir pos) == dir + , testProperty "diffPositions is add inverse" $ \(posโ :: Position) posโ -> + diffPositions posโ posโ == addPositions posโ (invert posโ) + , testGroup "isUnit" + [ testProperty "double direction is never unit" $ \dir -> + not . isUnit $ move dir (asPosition dir) + , testCase "examples" $ do + isUnit (Position @Int 1 1) @? "not . isUnit $ Position 1 1" + isUnit (Position @Int 0 (-1)) @? "not . isUnit $ Position 0 (-1)" + (not . isUnit) (Position @Int 1 13) @? "isUnit $ Position 1 13" + ] + ] + + , testGroup "Direction" + [ testProperty "opposite is involutive" $ \(dir :: Direction) -> + opposite (opposite dir) == dir + , testProperty "opposite provides inverse" $ \dir -> + invert (asPosition dir) === asPosition (opposite dir) + , testProperty "asPosition isUnit" $ \dir -> + dir /= Here ==> isUnit (asPosition dir) + , testGroup "Move" + [ testCase "Up" $ move Up mempty @?= Position @Int 0 (-1) + , testCase "Down" $ move Down mempty @?= Position @Int 0 1 + , testCase "Left" $ move Left mempty @?= Position @Int (-1) 0 + , testCase "Right" $ move Right mempty @?= Position @Int 1 0 + , testCase "UpLeft" $ move UpLeft mempty @?= Position @Int (-1) (-1) + , testCase "UpRight" $ move UpRight mempty @?= Position @Int 1 (-1) + , testCase "DownLeft" $ move DownLeft mempty @?= Position @Int (-1) 1 + , testCase "DownRight" $ move DownRight mempty @?= Position @Int 1 1 + ] + ] + + , testGroup "Corner" + [ testGroup "instance Opposite" + [ testProperty "involutive" $ \(corner :: Corner) -> + opposite (opposite corner) === corner + ] + ] + + , testGroup "Edge" + [ testGroup "instance Opposite" + [ testProperty "involutive" $ \(edge :: Edge) -> + opposite (opposite edge) === edge + ] + ] + + , testGroup "Box" + [ testGroup "boxIntersects" + [ testProperty "True" $ \dims -> + boxIntersects (Box @Word (V2 1 1) (V2 2 2)) + (Box (V2 2 2) dims) + , testProperty "False" $ \dims -> + not $ boxIntersects (Box @Word (V2 1 1) (V2 2 2)) + (Box (V2 4 2) dims) + ] + ] + + , testGroup "Neighbors" + [ testGroup "rotations" + [ testProperty "always has the same members" + $ \(neighs :: Neighbors Int) -> + all (\ns -> sort (toList ns) == sort (toList neighs)) + $ rotations neighs + , testProperty "all rotations have the same rotations" + $ \(neighs :: Neighbors Int) -> + let rots = rotations neighs + in all (\ns -> sort (toList $ rotations ns) == sort (toList rots)) + rots + ] + ] + + , testGroup "units" + [ testGroup "unit suffixes" + [ testCase "density" + $ tshow (10000 :: Grams `Per` Cubic Meters) @?= "10000.0 g/mยณ" + , testCase "volume" + $ tshow (5 :: Cubic Meters) @?= "5.0 mยณ" + , testCase "area" + $ tshow (5 :: Square Meters) @?= "5.0 mยฒ" + ] + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Entities/CharacterSpec.hs b/users/grfn/xanthous/test/Xanthous/Entities/CharacterSpec.hs new file mode 100644 index 000000000000..734cce1efbbe --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Entities/CharacterSpec.hs @@ -0,0 +1,24 @@ +{-# OPTIONS_GHC -Wno-type-defaults #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.CharacterSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Xanthous.Entities.Character +import Xanthous.Util (endoTimes) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Entities.CharacterSpec" + [ testGroup "Knuckles" + [ testBatch $ monoid @Knuckles mempty + , testGroup "damageKnuckles" + [ testCase "caps at 5" $ + let knuckles' = endoTimes 6 damageKnuckles mempty + in _knuckleDamage knuckles' @?= 5 + ] + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Entities/CommonSpec.hs b/users/grfn/xanthous/test/Xanthous/Entities/CommonSpec.hs new file mode 100644 index 000000000000..ba27e3cbca4e --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Entities/CommonSpec.hs @@ -0,0 +1,32 @@ +-------------------------------------------------------------------------------- +module Xanthous.Entities.CommonSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +import Data.Vector.Lens (toVectorOf) +-------------------------------------------------------------------------------- +import Xanthous.Entities.Common +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Entities.CommonSpec" + [ testGroup "Inventory" + [ testProperty "items === itemsWithPosition . _2" $ \inv -> + inv ^.. items === inv ^.. itemsWithPosition . _2 + , testGroup "removeItemFromPosition" $ + let rewield w inv = + let (old, inv') = inv & wielded <<.~ w + in inv' & backpack <>~ toVectorOf (wieldedItems . wieldedItem) old + in [ (Backpack, \item -> backpack %~ (item ^. wieldedItem <|)) + , (LeftHand, rewield . inLeftHand) + , (RightHand, rewield . inRightHand) + , (BothHands, rewield . review doubleHanded) + ] <&> \(pos, addItem) -> + testProperty (show pos) $ \inv item -> + let inv' = addItem item inv + inv'' = removeItemFromPosition pos (item ^. wieldedItem) inv' + in inv'' ^.. items === inv ^.. items + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Entities/RawTypesSpec.hs b/users/grfn/xanthous/test/Xanthous/Entities/RawTypesSpec.hs new file mode 100644 index 000000000000..e23f7faba3a6 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Entities/RawTypesSpec.hs @@ -0,0 +1,45 @@ +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Xanthous.Entities.RawTypesSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Data.Interval (Extended(..), (<=..<=)) +-------------------------------------------------------------------------------- +import Xanthous.Entities.RawTypes +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Entities.RawTypesSpec" + [ testGroup "CreatureGenerateParams" + [ testGroup "Ord laws" + [ testProperty "comparability" $ \(a :: CreatureGenerateParams) b -> + a <= b || b <= a + , testProperty "transitivity" $ \(a :: CreatureGenerateParams) b c -> + a <= b && b <= c ==> a <= c + , testProperty "reflexivity" $ \(a :: CreatureGenerateParams) -> + a <= a + , testProperty "antisymmetry" $ \(a :: CreatureGenerateParams) b -> + (a <= b && b <= a) == (a == b) + ] + , testGroup "canGenerate" $ + let makeParams minB maxB = + let _levelRange = maybe NegInf Finite minB <=..<= maybe PosInf Finite maxB + _equippedItem = Nothing + in CreatureGenerateParams {..} + in + [ testProperty "no bounds" $ \level -> + let gps = makeParams Nothing Nothing + in canGenerate level gps + , testProperty "min bound" $ \level minB -> + let gps = makeParams (Just minB) Nothing + in canGenerate level gps === (level >= minB) + , testProperty "max bound" $ \level maxB -> + let gps = makeParams Nothing (Just maxB) + in canGenerate level gps === (level <= maxB) + ] + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Entities/RawsSpec.hs b/users/grfn/xanthous/test/Xanthous/Entities/RawsSpec.hs new file mode 100644 index 000000000000..b6c80be51be7 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Entities/RawsSpec.hs @@ -0,0 +1,30 @@ +-- | + +module Xanthous.Entities.RawsSpec (main, test) where + +import Test.Prelude +import Xanthous.Entities.Raws +import Xanthous.Entities.RawTypes + (_Creature, entityName, generateParams, HasEquippedItem (equippedItem)) + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Entities.Raws" + [ testGroup "raws" + [ testCase "are all valid" $ raws `deepseq` pure () + , testCase "all CreatureEquippedItems reference existent entity names" $ + let notFound + = raws + ^.. folded + . _Creature + . generateParams + . _Just + . equippedItem + . _Just + . entityName + . filtered (isNothing . raw) + in null notFound @? ("Some entities weren't found: " <> show notFound) + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Game/PromptSpec.hs b/users/grfn/xanthous/test/Xanthous/Game/PromptSpec.hs new file mode 100644 index 000000000000..d7a3df4acafa --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Game/PromptSpec.hs @@ -0,0 +1,19 @@ +-------------------------------------------------------------------------------- +module Xanthous.Game.PromptSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Xanthous.Game.Prompt +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Game.PromptSpec" + [ testGroup "mkMenuItems" + [ testCase "with duplicate items" + $ mkMenuItems @[_] [('a', MenuOption @Int "a" 1), ('a', MenuOption "a" 2)] + @?= mapFromList [('a', MenuOption "a" 1), ('b', MenuOption "a" 2)] + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Game/StateSpec.hs b/users/grfn/xanthous/test/Xanthous/Game/StateSpec.hs new file mode 100644 index 000000000000..34584f73b2ad --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Game/StateSpec.hs @@ -0,0 +1,30 @@ +-------------------------------------------------------------------------------- +module Xanthous.Game.StateSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Xanthous.Game.State +import Xanthous.Entities.Raws (raws) +import Xanthous.Generators.Level.LevelContents (entityFromRaw) +import Control.Monad.Random (evalRandT) +import System.Random (getStdGen) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Game.StateSpec" + [ testGroup "entityTypeName" + [ testCase "for a creature" $ do + let gormlakRaw = raws ^?! ix "gormlak" + creature <- runRand $ entityFromRaw gormlakRaw + entityTypeName creature @?= "Creature" + , testCase "for an item" $ do + let stickRaw = raws ^?! ix "stick" + item <- runRand $ entityFromRaw stickRaw + entityTypeName item @?= "Item" + ] + ] + where + runRand x = evalRandT x =<< getStdGen diff --git a/users/grfn/xanthous/test/Xanthous/GameSpec.hs b/users/grfn/xanthous/test/Xanthous/GameSpec.hs new file mode 100644 index 000000000000..2fa8527d0e59 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/GameSpec.hs @@ -0,0 +1,55 @@ +module Xanthous.GameSpec where + +import Test.Prelude hiding (Down) +import Xanthous.Game +import Xanthous.Game.State +import Control.Lens.Properties +import Xanthous.Data (move, Direction(Down)) +import Xanthous.Data.EntityMap (atPosition) + +main :: IO () +main = defaultMain test + +test :: TestTree +test + = localOption (QuickCheckTests 10) + . localOption (QuickCheckMaxSize 10) + $ testGroup "Xanthous.Game" + [ testGroup "positionedCharacter" + [ testProperty "lens laws" $ isLens positionedCharacter + , testCase "updates the position of the character" $ do + initialGame <- getInitialState + let initialPos = initialGame ^. characterPosition + updatedGame = initialGame & characterPosition %~ move Down + updatedPos = updatedGame ^. characterPosition + updatedPos @?= move Down initialPos + updatedGame ^. entities . atPosition initialPos @?= fromList [] + updatedGame ^. entities . atPosition updatedPos + @?= fromList [SomeEntity $ initialGame ^. character] + ] + , testGroup "characterPosition" + [ testProperty "lens laws" $ isLens characterPosition + ] + , testGroup "character" + [ testProperty "lens laws" $ isLens character + ] + , testGroup "MessageHistory" + [ testGroup "MonoComonad laws" + [ testProperty "oextend oextract โก id" + $ \(mh :: MessageHistory) -> oextend oextract mh === mh + , testProperty "oextract โ oextend f โก f" + $ \(mh :: MessageHistory) f -> (oextract . oextend f) mh === f mh + , testProperty "oextend f โ oextend g โก oextend (f . oextend g)" + $ \(mh :: MessageHistory) f g -> + (oextend f . oextend g) mh === oextend (f . oextend g) mh + ] + ] + , testGroup "Saving the game" + [ testProperty "forms a prism" $ isPrism saved + , testProperty "round-trips" $ \gs -> + loadGame (saveGame gs) === Just gs + , testProperty "preserves the character ID" $ \gs -> + let Just gs' = loadGame $ saveGame gs + in gs' ^. character === gs ^. character + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Generators/Level/UtilSpec.hs b/users/grfn/xanthous/test/Xanthous/Generators/Level/UtilSpec.hs new file mode 100644 index 000000000000..b53c657f7559 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Generators/Level/UtilSpec.hs @@ -0,0 +1,127 @@ +{-# LANGUAGE PackageImports #-} +-------------------------------------------------------------------------------- +module Xanthous.Generators.Level.UtilSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +import System.Random (mkStdGen) +import Control.Monad.Random (runRandT) +import Data.Array.ST (STUArray, runSTUArray, thaw) +import Data.Array.IArray (bounds, array) +import Data.Array.MArray (newArray, readArray, writeArray) +import Data.Array (Array, range, listArray, Ix) +import Control.Monad.ST (ST, runST) +import "checkers" Test.QuickCheck.Instances.Array () +import Linear.V2 +-------------------------------------------------------------------------------- +import Xanthous.Util +import Xanthous.Data (width, height) +-------------------------------------------------------------------------------- +import Xanthous.Generators.Level.Util +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +-------------------------------------------------------------------------------- + +newtype GenArray a b = GenArray (Array a b) + deriving stock (Show, Eq) + +instance (Ix a, Arbitrary a, CoArbitrary a, Arbitrary b) + => Arbitrary (GenArray a b) where + arbitrary = GenArray <$> do + (mkElem :: a -> b) <- arbitrary + minDims <- arbitrary + maxDims <- arbitrary + let bnds = (minDims, maxDims) + pure $ listArray bnds $ mkElem <$> range bnds + +test :: TestTree +test = testGroup "Xanthous.Generators.Util" + [ testGroup "randInitialize" + [ testProperty "returns an array of the correct dimensions" + $ \dims seed aliveChance -> + let gen = mkStdGen seed + res = runSTUArray + $ fmap fst + $ flip runRandT gen + $ randInitialize dims aliveChance + in bounds res === (0, V2 (dims ^. width) (dims ^. height)) + ] + , testGroup "numAliveNeighborsM" + [ testProperty "maxes out at 8" + $ \(GenArray (arr :: Array (V2 Word) Bool)) loc -> + let + act :: forall s. ST s Word + act = do + mArr <- thaw @_ @_ @_ @(STUArray s) arr + numAliveNeighborsM mArr loc + res = runST act + in counterexample (show res) $ between 0 8 res + , testCase "on the outer x edge" $ + let act :: forall s. ST s Word + act = do + cells <- thaw @_ @_ @_ @(STUArray s) $ array @Array @Bool @(V2 Word) + (V2 0 0, V2 2 2) + [ (V2 0 0, True), (V2 1 0, True), (V2 2 0, True) + , (V2 0 1, False), (V2 1 1, False), (V2 2 1, True) + , (V2 0 2, True), (V2 1 2, True), (V2 2 2, True) + ] + numAliveNeighborsM cells (V2 0 1) + res = runST act + in res @?= 7 + , testCase "on the outer y edge" $ + let act :: forall s. ST s Word + act = do + cells <- thaw @_ @_ @_ @(STUArray s) $ array @Array @Bool @(V2 Word) + (V2 0 0, V2 2 2) + [ (V2 0 0, True), (V2 1 0, True), (V2 2 0, True) + , (V2 0 1, False), (V2 1 1, False), (V2 2 1, True) + , (V2 0 2, True), (V2 1 2, True), (V2 2 2, True) + ] + numAliveNeighborsM cells (V2 1 0) + res = runST act + in res @?= 6 + ] + , testGroup "numAliveNeighbors" + [ testProperty "is equivalient to runST . numAliveNeighborsM . thaw" $ + \(GenArray (arr :: Array (V2 Word) Bool)) loc -> + let + act :: forall s. ST s Word + act = do + mArr <- thaw @_ @_ @_ @(STUArray s) arr + numAliveNeighborsM mArr loc + res = runST act + in numAliveNeighbors arr loc === res + , testCase "on the outer x edge" $ + let cells = + array @Array @Bool @(V2 Word) + (V2 0 0, V2 2 2) + [ (V2 0 0, True), (V2 1 0, True), (V2 2 0, True) + , (V2 0 1, False), (V2 1 1, False), (V2 2 1, True) + , (V2 0 2, True), (V2 1 2, True), (V2 2 2, True) + ] + in numAliveNeighbors cells (V2 0 1) @?= 7 + , testCase "on the outer y edge" $ + let cells = + array @Array @Bool @(V2 Word) + (V2 0 0, V2 2 2) + [ (V2 0 0, True), (V2 1 0, True), (V2 2 0, True) + , (V2 0 1, False), (V2 1 1, False), (V2 2 1, True) + , (V2 0 2, True), (V2 1 2, True), (V2 2 2, True) + ] + in numAliveNeighbors cells (V2 1 0) @?= 6 + ] + , testGroup "cloneMArray" + [ testCase "clones the array" $ runST $ + let + go :: forall s. ST s Assertion + go = do + arr <- newArray @(STUArray s) (0 :: Int, 5) (1 :: Int) + arr' <- cloneMArray @_ @(STUArray s) arr + writeArray arr' 0 1234 + x <- readArray arr 0 + pure $ x @?= 1 + in go + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/MessageSpec.hs b/users/grfn/xanthous/test/Xanthous/MessageSpec.hs new file mode 100644 index 000000000000..2068e338bafe --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/MessageSpec.hs @@ -0,0 +1,59 @@ +{-# LANGUAGE OverloadedLists #-} +module Xanthous.MessageSpec ( main, test ) where + +import Test.Prelude +import Xanthous.Messages +import Data.Aeson +import Text.Mustache +import Control.Lens.Properties + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Messages" + [ testGroup "Message" + [ testGroup "JSON decoding" + [ testCase "Single" + $ decode "\"Test Single Template\"" + @?= Just (Single + $ compileMustacheText "template" "Test Single Template" + ^?! _Right) + , testCase "Choice" + $ decode "[\"Choice 1\", \"Choice 2\"]" + @?= Just + (Choice + [ compileMustacheText "template" "Choice 1" ^?! _Right + , compileMustacheText "template" "Choice 2" ^?! _Right + ]) + ] + ] + , localOption (QuickCheckTests 50) + . localOption (QuickCheckMaxSize 10) + $ testGroup "MessageMap" + [ testGroup "instance Ixed" + [ testProperty "traversal laws" $ \k -> + isTraversal $ ix @MessageMap k + , testCase "preview when exists" $ + let + Right tpl = compileMustacheText "foo" "bar" + msg = Single tpl + mm = Nested [("foo", Direct msg)] + in mm ^? ix ["foo"] @?= Just msg + ] + , testGroup "lookupMessage" + [ testProperty "is equivalent to preview ix" $ \msgMap path -> + lookupMessage path msgMap === msgMap ^? ix path + ] + ] + + , testGroup "Messages" + [ testCase "are all valid" $ messages `deepseq` pure () + ] + + , testGroup "Template" + [ testGroup "eq" + [ testProperty "reflexive" $ \(tpl :: Template) -> tpl == tpl + ] + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Messages/TemplateSpec.hs b/users/grfn/xanthous/test/Xanthous/Messages/TemplateSpec.hs new file mode 100644 index 000000000000..2a3873c3b016 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Messages/TemplateSpec.hs @@ -0,0 +1,80 @@ +-------------------------------------------------------------------------------- +module Xanthous.Messages.TemplateSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +import Test.QuickCheck.Instances.Text () +import Data.List.NonEmpty (NonEmpty(..)) +import Data.Function (fix) +-------------------------------------------------------------------------------- +import Xanthous.Messages.Template +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Messages.Template" + [ testGroup "parsing" + [ testProperty "literals" $ forAll genLiteral $ \s -> + testParse template s === Right (Literal s) + , parseCase "escaped curlies" + "foo\\{" + $ Literal "foo{" + , parseCase "simple substitution" + "foo {{bar}}" + $ Literal "foo " `Concat` Subst (SubstPath $ "bar" :| []) + , parseCase "substitution with filters" + "foo {{bar | baz}}" + $ Literal "foo " + `Concat` Subst (SubstFilter (SubstPath $ "bar" :| []) + (FilterName "baz")) + , parseCase "substitution with multiple filters" + "foo {{bar | baz | qux}}" + $ Literal "foo " + `Concat` Subst (SubstFilter (SubstFilter (SubstPath $ "bar" :| []) + (FilterName "baz")) + (FilterName "qux")) + , parseCase "two substitutions and a literal" + "{{a}}{{b}}c" + $ Subst (SubstPath $ "a" :| []) + `Concat` Subst (SubstPath $ "b" :| []) + `Concat` Literal "c" + , localOption (QuickCheckTests 10) + $ testProperty "round-trips with ppTemplate" $ \tpl -> + testParse template (ppTemplate tpl) === Right tpl + ] + , testBatch $ monoid @Template mempty + , testGroup "rendering" + [ testProperty "rendering literals renders literally" + $ forAll genLiteral $ \s fs vs -> + render fs vs (Literal s) === Right s + , testProperty "rendering substitutions renders substitutions" + $ forAll genPath $ \ident val fs -> + let tpl = Subst (SubstPath ident) + tvs = varsWith ident val + in render fs tvs tpl === Right val + , testProperty "filters filter" $ forAll genPath + $ \ident filterName filterFn val -> + let tpl = Subst (SubstFilter (SubstPath ident) filterName) + fs = mapFromList [(filterName, filterFn)] + vs = varsWith ident val + in render fs vs tpl === Right (filterFn val) + ] + ] + where + genLiteral = pack . filter (`notElem` ['\\', '{']) <$> arbitrary + parseCase name input expected = + testCase name $ testParse template input @?= Right expected + testParse p = over _Left errorBundlePretty . runParser p "<test>" + genIdentifier = pack @Text <$> listOf1 (elements identifierChars) + identifierChars = ['a'..'z'] <> ['A'..'Z'] <> ['-', '_'] + + varsWith (p :| []) val = vars [(p, Val val)] + varsWith (phead :| ps) val = vars . pure . (phead ,) . flip fix ps $ + \next pth -> case pth of + [] -> Val val + p : ps' -> nested [(p, next ps')] + + genPath = (:|) <$> genIdentifier <*> listOf genIdentifier + +-- diff --git a/users/grfn/xanthous/test/Xanthous/OrphansSpec.hs b/users/grfn/xanthous/test/Xanthous/OrphansSpec.hs new file mode 100644 index 000000000000..0d800e8a91de --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/OrphansSpec.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE OverloadedLists #-} +-------------------------------------------------------------------------------- +module Xanthous.OrphansSpec where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Text.Mustache +import Text.Megaparsec (errorBundlePretty) +import Graphics.Vty.Attributes +import qualified Data.Aeson as JSON +import Data.Interval (Interval, (<=..<=), (<=..<), (<..<=)) +import Data.Aeson ( ToJSON(toJSON), object, Value(Array) ) +import Data.Aeson.Types (fromJSON) +import Data.IntegerInterval (Extended(Finite)) +-------------------------------------------------------------------------------- +import Xanthous.Orphans +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Orphans" + [ localOption (QuickCheckTests 50) + . localOption (QuickCheckMaxSize 10) + $ testGroup "Template" + [ testProperty "ppTemplate / compileMustacheText " \tpl -> + let src = ppTemplate tpl + res :: Either String Template + res = over _Left errorBundlePretty + $ compileMustacheText (templateActual tpl) src + expected = templateCache tpl ^?! at (templateActual tpl) + in + counterexample (unpack src) + $ Right expected === do + (Template actual cache) <- res + maybe (Left "Template not found") Right $ cache ^? at actual + , testProperty "JSON round trip" $ \(tpl :: Template) -> + counterexample (unpack $ ppTemplate tpl) + $ JSON.decode (JSON.encode tpl) === Just tpl + ] + , testGroup "Attr" + [ jsonRoundTrip @Attr ] + , testGroup "Extended" + [ jsonRoundTrip @(Extended Int) ] + , testGroup "Interval" + [ testGroup "JSON" + [ jsonRoundTrip @(Interval Int) + , testCase "parses a single value as a length-1 interval" $ + getSuccess (fromJSON $ toJSON (1 :: Int)) + @?= Just (Finite (1 :: Int) <=..<= Finite 1) + , testCase "parses a pair of values as a single-ended interval" $ + getSuccess (fromJSON $ toJSON ([1, 2] :: [Int])) + @?= Just (Finite (1 :: Int) <=..< Finite (2 :: Int)) + , testCase "parses the full included/excluded syntax" $ + getSuccess (fromJSON $ Array [ object [ "Excluded" JSON..= (1 :: Int) ] + , object [ "Included" JSON..= (4 :: Int) ] + ]) + @?= Just (Finite (1 :: Int) <..<= Finite (4 :: Int)) + , testCase "parses open/closed as aliases" $ + getSuccess (fromJSON $ Array [ object [ "Open" JSON..= (1 :: Int) ] + , object [ "Closed" JSON..= (4 :: Int) ] + ]) + @?= Just (Finite (1 :: Int) <..<= Finite (4 :: Int)) + ] + ] + ] + where + getSuccess :: JSON.Result a -> Maybe a + getSuccess (JSON.Error _) = Nothing + getSuccess (JSON.Success r) = Just r diff --git a/users/grfn/xanthous/test/Xanthous/RandomSpec.hs b/users/grfn/xanthous/test/Xanthous/RandomSpec.hs new file mode 100644 index 000000000000..c88bd9562928 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/RandomSpec.hs @@ -0,0 +1,45 @@ +-------------------------------------------------------------------------------- +module Xanthous.RandomSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Control.Monad.Random +-------------------------------------------------------------------------------- +import Xanthous.Random +import Xanthous.Orphans () +import qualified Data.Interval as Interval +import Data.Interval (Interval, Extended (Finite), (<=..<=)) +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Random" + [ testGroup "chooseSubset" + [ testProperty "chooses a subset" + $ \(l :: [Int]) (Positive (r :: Double)) -> randomTest $ do + ss <- chooseSubset r l + pure $ all (`elem` l) ss + ] + , testGroup "chooseRange" + [ testProperty "chooses in the range" + $ \(rng :: Interval Int) -> + not (Interval.null rng) + ==> randomTest ( do + chooseRange rng >>= \case + Just r -> pure + . counterexample (show r) + $ r `Interval.member` rng + Nothing -> pure $ property Discard + ) + , testProperty "nonEmpty range is never empty" + $ \ (lower :: Int) (NonZero diff) -> randomTest $ do + let upper = lower + diff + r <- chooseRange (Finite lower <=..<= Finite upper) + pure $ isJust r + + ] + ] + where + randomTest prop = evalRandT prop . mkStdGen =<< arbitrary diff --git a/users/grfn/xanthous/test/Xanthous/Util/GraphSpec.hs b/users/grfn/xanthous/test/Xanthous/Util/GraphSpec.hs new file mode 100644 index 000000000000..35ff090b28b9 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Util/GraphSpec.hs @@ -0,0 +1,39 @@ +module Xanthous.Util.GraphSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude +-------------------------------------------------------------------------------- +import Xanthous.Util.Graph +import Data.Graph.Inductive.Basic +import Data.Graph.Inductive.Graph (labNodes, size, order) +import Data.Graph.Inductive.PatriciaTree +import Data.Graph.Inductive.Arbitrary +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Util.Graph" + [ testGroup "mstSubGraph" + [ testProperty "always produces a subgraph" + $ \(CG _ (graph :: Gr Int Int)) -> + let msg = mstSubGraph $ undir graph + in counterexample (show msg) + $ msg `isSubGraphOf` undir graph + , testProperty "returns a graph with the same nodes" + $ \(CG _ (graph :: Gr Int Int)) -> + let msg = mstSubGraph graph + in counterexample (show msg) + $ labNodes msg === labNodes graph + , testProperty "has nodes - 1 edges" + $ \(CG _ (graph :: Gr Int Int)) -> + order graph > 1 ==> + let msg = mstSubGraph graph + in counterexample (show msg) + $ size msg === order graph - 1 + , testProperty "always produces a simple graph" + $ \(CG _ (graph :: Gr Int Int)) -> + let msg = mstSubGraph graph + in counterexample (show msg) $ isSimple msg + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/Util/GraphicsSpec.hs b/users/grfn/xanthous/test/Xanthous/Util/GraphicsSpec.hs new file mode 100644 index 000000000000..61e589280362 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Util/GraphicsSpec.hs @@ -0,0 +1,72 @@ +module Xanthous.Util.GraphicsSpec (main, test) where +-------------------------------------------------------------------------------- +import Test.Prelude hiding (head) +-------------------------------------------------------------------------------- +import Data.List (nub, head) +import Data.Set (isSubsetOf) +import Linear.V2 +-------------------------------------------------------------------------------- +import Xanthous.Util.Graphics +import Xanthous.Util +import Xanthous.Orphans () +-------------------------------------------------------------------------------- + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Util.Graphics" + [ testGroup "circle" + [ testCase "radius 1, origin 2,2" + {- + | | 0 | 1 | 2 | 3 | + |---+---+---+---+---| + | 0 | | | | | + | 1 | | | x | | + | 2 | | x | | x | + | 3 | | | x | | + -} + $ (sort . unique @[] @[_]) (circle @Int (V2 2 2) 1) + @?= [ V2 1 2 + , V2 2 1, V2 2 3 + , V2 3 2 + ] + , testCase "radius 12, origin 0" + $ (sort . nub) (circle @Int 0 12) + @?= (sort . nub) + [ V2 (-12) (-4), V2 (-12) (-3), V2 (-12) (-2), V2 (-12) (-1) + , V2 (-12) 0, V2 (-12) 1, V2 (-12) 2, V2 (-12) 3, V2 (-12) 4 + , V2 (-11) (-6), V2 (-11) (-5), V2 (-11) 5, V2 (-11) 6, V2 (-10) (-7) + , V2 (-10) 7, V2 (-9) (-9), V2 (-9) (-8), V2 (-9) 8, V2 (-9) 9 + , V2 (-8) (-9), V2 (-8) 9, V2 (-7) (-10), V2 (-7) 10, V2 (-6) (-11) + , V2 (-6) 11, V2 (-5) (-11), V2 (-5) 11, V2 (-4) (-12), V2 (-4) 12 + , V2 (-3) (-12), V2 (-3) 12, V2 (-2) (-12), V2 (-2) 12, V2 (-1) (-12) + , V2 (-1) 12, V2 0 (-12), V2 0 12, V2 1 (-12), V2 1 12, V2 2 (-12) + , V2 2 12, V2 3 (-12), V2 3 12, V2 4 (-12), V2 4 12, V2 5 (-11) + , V2 5 11, V2 6 (-11), V2 6 11, V2 7 (-10), V2 7 10, V2 8 (-9), V2 8 9 + , V2 9 (-9), V2 9 (-8), V2 9 8, V2 9 9, V2 10 (-7), V2 10 7 + , V2 11 (-6), V2 11 (-5), V2 11 5, V2 11 6, V2 12 (-4), V2 12 (-3) + , V2 12 (-2), V2 12 (-1), V2 12 0, V2 12 1, V2 12 2, V2 12 3, V2 12 4 + ] + ] + , testGroup "filledCircle" + [ testProperty "is a superset of circle" $ \center radius -> + let circ = circle @Int center radius + filledCirc = filledCircle center radius + in counterexample ( "circle: " <> show circ + <> "\nfilledCircle: " <> show filledCirc) + $ setFromList circ `isSubsetOf` setFromList filledCirc + -- TODO later + -- , testProperty "is always contiguous" $ \center radius -> + -- let filledCirc = filledCircle center radius + -- in counterexample (renderBooleanGraphics filledCirc) $ + ] + , testGroup "line" + [ testProperty "starts and ends at the start and end points" $ \start end -> + let โ = line @Int start end + in counterexample ("line: " <> show โ) + $ length โ > 2 ==> (head โ === start) .&&. (head (reverse โ) === end) + ] + ] + +-------------------------------------------------------------------------------- diff --git a/users/grfn/xanthous/test/Xanthous/Util/InflectionSpec.hs b/users/grfn/xanthous/test/Xanthous/Util/InflectionSpec.hs new file mode 100644 index 000000000000..fad841043152 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/Util/InflectionSpec.hs @@ -0,0 +1,18 @@ +module Xanthous.Util.InflectionSpec (main, test) where + +import Test.Prelude +import Xanthous.Util.Inflection + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Util.Inflection" + [ testGroup "toSentence" + [ testCase "empty" $ toSentence [] @?= "" + , testCase "single" $ toSentence ["x"] @?= "x" + , testCase "two" $ toSentence ["x", "y"] @?= "x and y" + , testCase "three" $ toSentence ["x", "y", "z"] @?= "x, y, and z" + , testCase "four" $ toSentence ["x", "y", "z", "w"] @?= "x, y, z, and w" + ] + ] diff --git a/users/grfn/xanthous/test/Xanthous/UtilSpec.hs b/users/grfn/xanthous/test/Xanthous/UtilSpec.hs new file mode 100644 index 000000000000..684a03b2c7a0 --- /dev/null +++ b/users/grfn/xanthous/test/Xanthous/UtilSpec.hs @@ -0,0 +1,46 @@ +module Xanthous.UtilSpec (main, test) where + +import Test.Prelude +import Xanthous.Util +import Control.Monad.State.Lazy (execState) + +main :: IO () +main = defaultMain test + +test :: TestTree +test = testGroup "Xanthous.Util" + [ testGroup "smallestNotIn" + [ testCase "examples" $ do + smallestNotIn [7 :: Word, 3, 7] @?= 0 + smallestNotIn [7 :: Word, 0, 1, 3, 7] @?= 2 + , testProperty "returns an element not in the list" $ \(xs :: [Word]) -> + smallestNotIn xs `notElem` xs + , testProperty "pred return is in the list" $ \(xs :: [Word]) -> + let res = smallestNotIn xs + in res /= 0 ==> pred res `elem` xs + , testProperty "ignores order" $ \(xs :: [Word]) -> + forAll (shuffle xs) $ \shuffledXs -> + smallestNotIn xs === smallestNotIn shuffledXs + ] + , testGroup "takeWhileInclusive" + [ testProperty "takeWhileInclusive (const True) โก id" + $ \(xs :: [Int]) -> takeWhileInclusive (const True) xs === xs + ] + , testGroup "endoTimes" + [ testCase "endoTimes 4 succ 5" + $ endoTimes (4 :: Int) succ (5 :: Int) @?= 9 + ] + , testGroup "modifyKL" + [ testCase "_1 += 1" + $ execState (modifyKL _1 $ pure . succ) (1 :: Int, 2 :: Int) @?= (2, 2) + ] + , testGroup "removeFirst" + [ testCase "example" $ + removeFirst @[Int] (> 5) [1..10] @?= [1, 2, 3, 4, 5, 7, 8, 9, 10] + , testProperty "the result is the right length" $ \(xs :: [Int]) p -> + length (removeFirst p xs) `elem` [length xs, length xs - 1] + ] + , testGroup "AlphaChar" + [ testCase "succ 'z'" $ succ (AlphaChar 'z') @?= AlphaChar 'A' + ] + ] diff --git a/users/grfn/xanthous/xanthous.cabal b/users/grfn/xanthous/xanthous.cabal new file mode 100644 index 000000000000..987e1f48f693 --- /dev/null +++ b/users/grfn/xanthous/xanthous.cabal @@ -0,0 +1,528 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.34.5. +-- +-- see: https://github.com/sol/hpack +-- +-- hash: 8cae8550487b6092c18c82a0dc29bf22980d416771c66f6fca3e151875c66495 + +name: xanthous +version: 0.1.0.0 +synopsis: A WIP TUI RPG +description: Please see the README on GitHub at <https://github.com/glittershark/xanthous> +category: Game +homepage: https://github.com/glittershark/xanthous#readme +bug-reports: https://github.com/glittershark/xanthous/issues +author: Griffin Smith +maintainer: root@gws.fyi +copyright: 2019 Griffin Smith +license: GPL-3 +license-file: LICENSE +build-type: Simple +extra-source-files: + README.org + +source-repository head + type: git + location: https://github.com/glittershark/xanthous + +library + exposed-modules: + Data.Aeson.Generic.DerivingVia + Xanthous.AI.Gormlak + Xanthous.App + Xanthous.App.Autocommands + Xanthous.App.Common + Xanthous.App.Prompt + Xanthous.App.Time + Xanthous.Command + Xanthous.Data + Xanthous.Data.App + Xanthous.Data.Entities + Xanthous.Data.EntityChar + Xanthous.Data.EntityMap + Xanthous.Data.EntityMap.Graphics + Xanthous.Data.Levels + Xanthous.Data.Memo + Xanthous.Data.NestedMap + Xanthous.Data.VectorBag + Xanthous.Entities.Character + Xanthous.Entities.Common + Xanthous.Entities.Creature + Xanthous.Entities.Creature.Hippocampus + Xanthous.Entities.Draw.Util + Xanthous.Entities.Entities + Xanthous.Entities.Environment + Xanthous.Entities.Item + Xanthous.Entities.Marker + Xanthous.Entities.Raws + Xanthous.Entities.RawTypes + Xanthous.Game + Xanthous.Game.Arbitrary + Xanthous.Game.Draw + Xanthous.Game.Env + Xanthous.Game.Lenses + Xanthous.Game.Memo + Xanthous.Game.Prompt + Xanthous.Game.State + Xanthous.Generators.Level + Xanthous.Generators.Level.CaveAutomata + Xanthous.Generators.Level.Dungeon + Xanthous.Generators.Level.LevelContents + Xanthous.Generators.Level.Util + Xanthous.Generators.Level.Village + Xanthous.Generators.Speech + Xanthous.Messages + Xanthous.Messages.Template + Xanthous.Monad + Xanthous.Orphans + Xanthous.Physics + Xanthous.Prelude + Xanthous.Random + Xanthous.Util + Xanthous.Util.Comonad + Xanthous.Util.Graph + Xanthous.Util.Graphics + Xanthous.Util.Inflection + Xanthous.Util.JSON + Xanthous.Util.Optparse + Xanthous.Util.QuickCheck + other-modules: + Paths_xanthous + hs-source-dirs: + src + default-extensions: + BlockArguments + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DerivingVia + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTSyntax + GeneralizedNewtypeDeriving + KindSignatures + StandaloneKindSignatures + LambdaCase + MultiWayIf + NoImplicitPrelude + NoStarIsType + OverloadedStrings + PolyKinds + RankNTypes + ScopedTypeVariables + TupleSections + TypeApplications + TypeFamilies + TypeOperators + ViewPatterns + ghc-options: -Wall + build-depends: + JuicyPixels + , MonadRandom + , QuickCheck + , Rasterific + , aeson + , array + , async + , base + , bifunctors + , brick + , checkers + , classy-prelude + , comonad + , comonad-extras + , constraints + , containers + , criterion + , data-default + , data-interval + , deepseq + , directory + , fgl + , fgl-arbitrary + , file-embed + , filepath + , generic-arbitrary + , generic-lens + , groups + , hgeometry + , hgeometry-combinatorial + , lens + , lifted-async + , linear + , megaparsec + , mmorph + , monad-control + , mtl + , optparse-applicative + , parallel + , parser-combinators + , pointed + , quickcheck-instances + , quickcheck-text + , random + , random-extras + , random-fu + , random-source + , raw-strings-qq + , reflection + , semigroupoids + , semigroups + , splitmix + , stache + , streams + , text + , text-zipper + , tomland + , transformers + , vector + , vty + , witherable + , yaml + , zlib + default-language: Haskell2010 + +executable xanthous + main-is: Main.hs + other-modules: + Paths_xanthous + hs-source-dirs: + app + default-extensions: + BlockArguments + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DerivingVia + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTSyntax + GeneralizedNewtypeDeriving + KindSignatures + StandaloneKindSignatures + LambdaCase + MultiWayIf + NoImplicitPrelude + NoStarIsType + OverloadedStrings + PolyKinds + RankNTypes + ScopedTypeVariables + TupleSections + TypeApplications + TypeFamilies + TypeOperators + ViewPatterns + ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -O2 + build-depends: + JuicyPixels + , MonadRandom + , QuickCheck + , Rasterific + , aeson + , array + , async + , base + , bifunctors + , brick + , checkers + , classy-prelude + , comonad + , comonad-extras + , constraints + , containers + , criterion + , data-default + , data-interval + , deepseq + , directory + , fgl + , fgl-arbitrary + , file-embed + , filepath + , generic-arbitrary + , generic-lens + , groups + , hgeometry + , hgeometry-combinatorial + , lens + , lifted-async + , linear + , megaparsec + , mmorph + , monad-control + , mtl + , optparse-applicative + , parallel + , parser-combinators + , pointed + , quickcheck-instances + , quickcheck-text + , random + , random-extras + , random-fu + , random-source + , raw-strings-qq + , reflection + , semigroupoids + , semigroups + , splitmix + , stache + , streams + , text + , text-zipper + , tomland + , transformers + , vector + , vty + , witherable + , xanthous + , yaml + , zlib + default-language: Haskell2010 + +test-suite test + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Test.Prelude + Xanthous.Data.EntitiesSpec + Xanthous.Data.EntityCharSpec + Xanthous.Data.EntityMap.GraphicsSpec + Xanthous.Data.EntityMapSpec + Xanthous.Data.LevelsSpec + Xanthous.Data.MemoSpec + Xanthous.Data.NestedMapSpec + Xanthous.DataSpec + Xanthous.Entities.CharacterSpec + Xanthous.Entities.CommonSpec + Xanthous.Entities.RawsSpec + Xanthous.Entities.RawTypesSpec + Xanthous.Game.PromptSpec + Xanthous.Game.StateSpec + Xanthous.GameSpec + Xanthous.Generators.Level.UtilSpec + Xanthous.Messages.TemplateSpec + Xanthous.MessageSpec + Xanthous.OrphansSpec + Xanthous.RandomSpec + Xanthous.Util.GraphicsSpec + Xanthous.Util.GraphSpec + Xanthous.Util.InflectionSpec + Xanthous.UtilSpec + Paths_xanthous + hs-source-dirs: + test + default-extensions: + BlockArguments + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DerivingVia + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTSyntax + GeneralizedNewtypeDeriving + KindSignatures + StandaloneKindSignatures + LambdaCase + MultiWayIf + NoImplicitPrelude + NoStarIsType + OverloadedStrings + PolyKinds + RankNTypes + ScopedTypeVariables + TupleSections + TypeApplications + TypeFamilies + TypeOperators + ViewPatterns + ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -O0 + build-depends: + JuicyPixels + , MonadRandom + , QuickCheck + , Rasterific + , aeson + , array + , async + , base + , bifunctors + , brick + , checkers + , classy-prelude + , comonad + , comonad-extras + , constraints + , containers + , criterion + , data-default + , data-interval + , deepseq + , directory + , fgl + , fgl-arbitrary + , file-embed + , filepath + , generic-arbitrary + , generic-lens + , groups + , hgeometry + , hgeometry-combinatorial + , lens + , lens-properties + , lifted-async + , linear + , megaparsec + , mmorph + , monad-control + , mtl + , optparse-applicative + , parallel + , parser-combinators + , pointed + , quickcheck-instances + , quickcheck-text + , random + , random-extras + , random-fu + , random-source + , raw-strings-qq + , reflection + , semigroupoids + , semigroups + , splitmix + , stache + , streams + , tasty + , tasty-hunit + , tasty-quickcheck + , tasty-rerun + , text + , text-zipper + , tomland + , transformers + , vector + , vty + , witherable + , xanthous + , yaml + , zlib + default-language: Haskell2010 + +benchmark benchmark + type: exitcode-stdio-1.0 + main-is: Bench.hs + other-modules: + Bench.Prelude + Xanthous.Generators.UtilBench + Xanthous.RandomBench + Paths_xanthous + hs-source-dirs: + bench + default-extensions: + BlockArguments + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DerivingVia + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTSyntax + GeneralizedNewtypeDeriving + KindSignatures + StandaloneKindSignatures + LambdaCase + MultiWayIf + NoImplicitPrelude + NoStarIsType + OverloadedStrings + PolyKinds + RankNTypes + ScopedTypeVariables + TupleSections + TypeApplications + TypeFamilies + TypeOperators + ViewPatterns + ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N + build-depends: + JuicyPixels + , MonadRandom + , QuickCheck + , Rasterific + , aeson + , array + , async + , base + , bifunctors + , brick + , checkers + , classy-prelude + , comonad + , comonad-extras + , constraints + , containers + , criterion + , data-default + , data-interval + , deepseq + , directory + , fgl + , fgl-arbitrary + , file-embed + , filepath + , generic-arbitrary + , generic-lens + , groups + , hgeometry + , hgeometry-combinatorial + , lens + , lifted-async + , linear + , megaparsec + , mmorph + , monad-control + , mtl + , optparse-applicative + , parallel + , parser-combinators + , pointed + , quickcheck-instances + , quickcheck-text + , random + , random-extras + , random-fu + , random-source + , raw-strings-qq + , reflection + , semigroupoids + , semigroups + , splitmix + , stache + , streams + , text + , text-zipper + , tomland + , transformers + , vector + , vty + , witherable + , xanthous + , yaml + , zlib + default-language: Haskell2010 diff --git a/users/isomer/OWNERS b/users/isomer/OWNERS new file mode 100644 index 000000000000..6997cd391d9c --- /dev/null +++ b/users/isomer/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - isomer diff --git a/users/isomer/keys.nix b/users/isomer/keys.nix new file mode 100644 index 000000000000..8c29e27895db --- /dev/null +++ b/users/isomer/keys.nix @@ -0,0 +1,7 @@ +# SSH public keys +{ ... }: + +rec { + perry = "cert-authority,principals=perry ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCXWKN+FXlQAQ36R4+FHJ9f15Tz/48xLK1f85Yf9eBrvJJVMn6ge3Cy8AJ2nymBtVvCC86q616yl4Mn+CrKBH/vHr4jY9nxJ7HHgKI8ERr+7KpLIAiiaeIBljWwCy918lK3MijRCuj0P0d3v8CEFJjyCsiyglDVcNhsW87VqqZE6lUg4Alw1CGAmNjamxdoIZxjZAM9vJtZrlYnUiu+X7vTl5ttTaZkLCCfu+/bJAKFBWPG5BPaNjjfGVuTKqEc4plkI3JeZBu3Or3LzlYxcvp71i+eKGJ8F/nMBlo25iQsQpi8ZS7JYAhj3mYVrstw7j+nkgbordvDOK5NbDMi6GzX"; + all = [ perry ]; +} diff --git a/users/lukegb/OWNERS b/users/lukegb/OWNERS new file mode 100644 index 000000000000..676fbf185649 --- /dev/null +++ b/users/lukegb/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - lukegb diff --git a/users/lukegb/hgext/gerrithook.py b/users/lukegb/hgext/gerrithook.py new file mode 100644 index 000000000000..ef02126ba0f8 --- /dev/null +++ b/users/lukegb/hgext/gerrithook.py @@ -0,0 +1,63 @@ +"""Bizarre hacks to make Gerrit better.""" + +import collections +import re +import random +import mercurial + +_ = mercurial.i18n._ + +cmdtable = {} +command = mercurial.registrar.command(cmdtable) + +testedwith = '5.3.1' + +_changeid_regex = re.compile(b'^Change-Id: (I.*)$', re.M) + +def random_hash(): + """Returns a random SHA1-like hex string.""" + return b"%040x" % random.getrandbits(160) + +def reposetup(ui, repo): + + class GerritRepo(repo.__class__): + def commitctx(self, ctx, *args, **kwargs): + match = _changeid_regex.search(ctx._text) + if not match: + ctx._text = ctx._text.rstrip(b'\n') + ctx._text += b'\n\nChange-Id: I' + random_hash() + return super().commitctx(ctx, *args, **kwargs) + + repo.__class__ = GerritRepo + + +@command(b'gerrit-obsolete', [], _(b'[options]')) +def gerritobsolete(ui, repo, **opts): + """Mark draft commits as obsolete by public commits based on Gerrit Change-Id tag.""" + if repo.obsstore.readonly: + ui.error(b'obsstore is readonly') + return + changesets = collections.defaultdict(set) + drafts = set() + for draft in repo.set('draft() - obsolete()'): + match = _changeid_regex.search(draft.description()) + if not match: + continue + changesets[match.groups()[0]].add(draft) + drafts.add(draft) + if not drafts: + return + publicparent = next(repo.set( + b'ancestor((public() and bookmark("canon")), %s)' % ( + b', '.join(x.hex() for x in drafts)))) + megare = b're:(?ms)^Change-Id: (%s)$' % (b'|'.join(changesets.keys()),) + markers = [] + for public in repo.set('(%s..(public() and canon)) and desc(%s)', publicparent, megare): + match = _changeid_regex.search(public.description()) + if not match: + continue + drafts = changesets[match.groups()[0]] + if not drafts: + continue + markers.append((tuple(drafts), (public,))) + mercurial.obsolete.createmarkers(repo, markers, operation=b'gerrit-obsolete') diff --git a/users/lukegb/keys.nix b/users/lukegb/keys.nix new file mode 100644 index 000000000000..e54009122f92 --- /dev/null +++ b/users/lukegb/keys.nix @@ -0,0 +1,10 @@ +# My SSH public keys +{ ... }: + +rec { + termius = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINytpHct7PLdLNp6MoaOPP7ccBPUQKymVNMqix//Wt1f"; + porcorosso-wsl = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMhQ3yjf59eQjOfVXzXz5u8BS5c6hdL1yY8GqccaIjx3"; + porcorosso-nixos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILid+1rq3k3k7Kbaw8X63vrPrQdanH55TucQwp3ZWfo+"; + clouvider-lon01-nix = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQU7Y+Ha5m0ebwUjA55xXT/xbWZAWx1fVNFufle+vQj"; + all = [ termius porcorosso-wsl porcorosso-nixos clouvider-lon01-nix ]; +} diff --git a/users/qyliss/OWNERS b/users/qyliss/OWNERS new file mode 100644 index 000000000000..d54ea3622d21 --- /dev/null +++ b/users/qyliss/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - qyliss diff --git a/users/qyliss/keys.nix b/users/qyliss/keys.nix new file mode 100644 index 000000000000..d0837a7c6744 --- /dev/null +++ b/users/qyliss/keys.nix @@ -0,0 +1,8 @@ +# Public key from https://github.com/alyssais.keys +{ ... }: + +{ + all = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDO11Pr7jKaRZ2It1yB312SKFN8mCV7aVYdry16LNwtnA6EDgFxyshG4Zmhl9asxQ9wa1lT3tdKB6ArA+VKxXMZB0zm15jYSLKpHQxMT7T3SqtTluJQpJD9zRtWeHbW/e1mtgn3tPYTHERB4HVGKIeGk97eOR2YOdXPHOIWhOXpogDtUlyt1bmWl0gyRHbWhViLeReHYhsu0KbZlo+ntN9aN7lPVkDfa7gUARv6IeGE5hAYHPRWmQ3VJCDaQnzsTtesLPFiNmV6Pq7qtWbHVNOG9XQLXJhD/305+yDZ2y/+KuBEQCroiWF8fPY/8gutfkZ0ZLjdGbXl38j5v+yRjreh+wjcN5MYWCWM18hMdutpoMd9D7PXaZz90V2vS+mRC81t3zXKrAy3Ke+LQBmlWSWxmKWdDoOTGOHjyPuCC/q+In7Q8hetB9/b9WUXTwEaaE3lUsa7y5JHAekNmdSoN3WD10nGYVUMvRRPGAlyqZTQdvxhn+6Pyu2piwIv/TMmC1CwiHr+fLbHxXQF745sOBQNmrdfiOzqDsKleybNB6i0AdDm5UZcYRcMLuxmryxN8O8qNUdMjMGoCeFcGwAIieqM+0xkPiByKr8ky2yV2lwOaZ4jrp/3j5GsGoQlvNKIPdCA/GQFad6vuqvhlbWcbdfiNpawrppLcJBsGB2NVjGbNQ==" + ]; +} diff --git a/users/riking/OWNERS b/users/riking/OWNERS new file mode 100644 index 000000000000..a39f4cd9f0ae --- /dev/null +++ b/users/riking/OWNERS @@ -0,0 +1,3 @@ +inherit: false +owners: + - riking diff --git a/users/riking/adventofcode-2020/.gitignore b/users/riking/adventofcode-2020/.gitignore new file mode 100644 index 000000000000..076ff412156a --- /dev/null +++ b/users/riking/adventofcode-2020/.gitignore @@ -0,0 +1,2 @@ +*/target +*/input.txt diff --git a/users/riking/adventofcode-2020/day01/Cargo.lock b/users/riking/adventofcode-2020/day01/Cargo.lock new file mode 100644 index 000000000000..a1a18948a7ea --- /dev/null +++ b/users/riking/adventofcode-2020/day01/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" + +[[package]] +name = "day01" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/users/riking/adventofcode-2020/day01/Cargo.toml b/users/riking/adventofcode-2020/day01/Cargo.toml new file mode 100644 index 000000000000..d90ab548bb7b --- /dev/null +++ b/users/riking/adventofcode-2020/day01/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day01" +version = "0.1.0" +authors = ["Kane York <kanepyork@gmail.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.34" diff --git a/users/riking/adventofcode-2020/day01/default.nix b/users/riking/adventofcode-2020/day01/default.nix new file mode 100644 index 000000000000..0648a05af683 --- /dev/null +++ b/users/riking/adventofcode-2020/day01/default.nix @@ -0,0 +1,10 @@ +{ depot, ... }: + +with depot.third_party; + +naersk.buildPackage { + src = ./.; + + buildInputs = []; + doCheck = true; +} diff --git a/users/riking/adventofcode-2020/day01/src/main.rs b/users/riking/adventofcode-2020/day01/src/main.rs new file mode 100644 index 000000000000..3e6b339d7c2b --- /dev/null +++ b/users/riking/adventofcode-2020/day01/src/main.rs @@ -0,0 +1,85 @@ +use anyhow::anyhow; +use std::fs::File; +use std::io::prelude::*; +use std::io::BufReader; + +const PART_2: bool = true; + +fn day01(is_part2: bool, numbers: &Vec<i64>) -> Result<String, anyhow::Error> { + //println!("{:?}", numbers); + + for n1 in numbers.iter() { + for n2 in numbers.iter() { + if is_part2 { + for n3 in numbers.iter() { + if n1 + n2 + n3 == 2020 { + return Ok((n1 * n2 * n3).to_string()); + } + } + } else { + if n1 + n2 == 2020 { + return Ok((n1 * n2).to_string()); + } + } + } + } + + Err(anyhow!("no solution found")) +} + +fn parse(filename: &str) -> Result<Vec<i64>, anyhow::Error> { + let f = File::open(filename)?; + let mut reader = BufReader::new(f); + + let mut values = Vec::<i64>::new(); + + let mut line = String::new(); + loop { + line.clear(); + reader.read_line(&mut line)?; + let trimmed_line = line.trim(); + if trimmed_line.is_empty() { + break; + } + + values.push(trimmed_line.parse()?); + } + Ok(values) +} + +fn main() -> anyhow::Result<()> { + let args: Vec<String> = std::env::args().collect(); + + //println!("{:?}", args); + if args.len() != 2 { + return Err(anyhow!("usage: day01 input_file")); + } + let filename = args.into_iter().skip(1).next().expect("args len == 1"); + + let numbers = parse(&filename)?; + + println!("{}", day01(PART_2, &numbers)?); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::day01; + + #[test] + fn test_part1() { + let vec = vec![1721, 979, 366, 299, 675, 1456]; + let result = day01(false, &vec).unwrap(); + + assert_eq!(result, 514579.to_string()); + } + + #[test] + fn test_part2() { + let vec = vec![1721, 979, 366, 299, 675, 1456]; + let result = day01(true, &vec).unwrap(); + + assert_eq!(result, 241861950.to_string()); + } +} diff --git a/users/riking/dotfiles/.mybashrc b/users/riking/dotfiles/.mybashrc new file mode 100644 index 000000000000..c5ebc34a1f4f --- /dev/null +++ b/users/riking/dotfiles/.mybashrc @@ -0,0 +1,53 @@ + +# BEGIN: __USER_FUNCTIONS__ +function gh-clone() { + if [[ "x$2" == "x" ]]; then + IFS='/' read -ra PARTS <<< "$1" + user="${PARTS[0]}" + repo="${PARTS[1]}" + else + user="$1" + repo="$2" + fi + if [[ -d ~/go/src/github.com/"$user"/"$repo" ]]; then + cd ~/go/src/github.com/"${user}"/"${repo}" + return 0 + fi + mkdir -p ~/go/src/github.com/"${user}" + cd ~/go/src/github.com/"${user}" + git clone git@github.com:"${user}"/"${repo}".git + cd ~/go/src/github.com/"${user}"/"${repo}" +} + +function download() { + cd "${HOME}/Downloads" + wget "$@" +} + +# todo: only one password pls +function prodaccess() { + (ssh-add -L | grep -q 'ZgEu6S3SLatYN') || ssh-add "$HOME"/.ssh/id_ed25519 + (ssh-add -L | grep -q 'Gfh2S3kUwZ8A6') || ssh-add "$HOME"/.ssh/id_rsa.discourse + echo "signing test" | gpg --clearsign > /dev/null +} + +function reset-audio() { + pulseaudio -k && sudo alsa force-reload +} + +function tvl-push() { + git push origin HEAD:refs/for/canon +} + +# END: __USER_FUNCTIONS__ + +# BEGIN: __USER_ENV__ +GOPATH=$HOME/go +CDPATH=$HOME/go/src +export GPG_TTY="$(tty)" + +export PATH="/usr/local/go/bin:$HOME/go/bin:$HOME/.rbenv/bin:$PATH" + +eval "$(rbenv init -)" +# END: __USER_ENV__ + diff --git a/users/riking/dotfiles/fish/conf.d/nix-env.fish b/users/riking/dotfiles/fish/conf.d/nix-env.fish new file mode 100644 index 000000000000..6f79f9752855 --- /dev/null +++ b/users/riking/dotfiles/fish/conf.d/nix-env.fish @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: Unlicense +# https://raw.githubusercontent.com/lilyball/nix-env.fish/master/conf.d/nix-env.fish + +# Setup Nix + +# We need to distinguish between single-user and multi-user installs. +# This is difficult because there's no official way to do this. +# We could look for the presence of /nix/var/nix/daemon-socket/socket but this will fail if the +# daemon hasn't started yet. /nix/var/nix/daemon-socket will exist if the daemon has ever run, but +# I don't think there's any protection against accidentally running `nix-daemon` as a user. +# We also can't just look for /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh because +# older single-user installs used the default profile instead of a per-user profile. +# We can still check for it first, because all multi-user installs should have it, and so if it's +# not present that's a pretty big indicator that this is a single-user install. If it does exist, +# we still need to verify the install type. To that end we'll look for a root owner and sticky bit +# on /nix/store. Multi-user installs set both, single-user installs don't. It's certainly possible +# someone could do a single-user install as root and then manually set the sticky bit but that +# would be extremely unusual. + +set -l nix_profile_path /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh +set -l single_user_profile_path ~/.nix-profile/etc/profile.d/nix.sh +if test -e $nix_profile_path + # The path exists. Double-check that this is a multi-user install. + # We can't just check for ~/.nix-profile/โฆ because this may be a single-user install running as + # the wrong user. + + # stat is not portable. Splitting the output of ls -nd is reliable on most platforms. + set -l owner (string split -n ' ' (ls -nd /nix/store 2>/dev/null))[3] + if not test -k /nix/store -a $owner -eq 0 + # /nix/store is either not owned by root or not sticky. Assume single-user. + set nix_profile_path $single_user_profile_path + end +else + # The path doesn't exist. Assume single-user + set nix_profile_path $single_user_profile_path +end + +if test -e $nix_profile_path + # Source the nix setup script + # We're going to run the regular Nix profile under bash and then print out a few variables + for line in (env -u BASH_ENV bash -c '. "$0"; for name in PATH "${!NIX_@}"; do printf "%s=%s\0" "$name" "${!name}"; done' $nix_profile_path | string split0) + set -xg (string split -m 1 = $line) + end + + # Insert Nix's fish share directories into fish's special variables. + # nixpkgs-installed fish tries to set these up already if NIX_PROFILES is defined, which won't + # be the case when sourcing $__fish_data_dir/share/config.fish normally, but might be for a + # recursive invocation. To guard against that, we'll only insert paths that don't already exit. + # Furthermore, for the vendor_conf.d sourcing, we'll use the pre-existing presence of a path in + # $fish_function_path to determine whether we want to source the relevant vendor_conf.d folder. + + # To start, let's locally define NIX_PROFILES if it doesn't already exist. + set -al NIX_PROFILES + if test (count $NIX_PROFILES) -eq 0 + set -a NIX_PROFILES $HOME/.nix-profile + end + # Replicate the logic from nixpkgs version of $__fish_data_dir/__fish_build_paths.fish. + set -l __nix_profile_paths (string split ' ' -- $NIX_PROFILES)[-1..1] + set -l __extra_completionsdir \ + $__nix_profile_paths/etc/fish/completions \ + $__nix_profile_paths/share/fish/vendor_completions.d + set -l __extra_functionsdir \ + $__nix_profile_paths/etc/fish/functions \ + $__nix_profile_paths/share/fish/vendor_functions.d + set -l __extra_confdir \ + $__nix_profile_paths/etc/fish/conf.d \ + $__nix_profile_paths/share/fish/vendor_conf.d \ + + ### Configure fish_function_path ### + # Remove any of our extra paths that may already exist. + # Record the equivalent __extra_confdir path for any function path that exists. + set -l existing_conf_paths + for path in $__extra_functionsdir + if set -l idx (contains --index -- $path $fish_function_path) + set -e fish_function_path[$idx] + set -a existing_conf_paths $__extra_confdir[(contains --index -- $path $__extra_functionsdir)] + end + end + # Insert the paths before $__fish_data_dir. + if set -l idx (contains --index -- $__fish_data_dir/functions $fish_function_path) + # Fish has no way to simply insert into the middle of an array. + set -l new_path $fish_function_path[1..$idx] + set -e new_path[$idx] + set -a new_path $__extra_functionsdir + set fish_function_path $new_path $fish_function_path[$idx..-1] + else + set -a fish_function_path $__extra_functionsdir + end + + ### Configure fish_complete_path ### + # Remove any of our extra paths that may already exist. + for path in $__extra_completionsdir + if set -l idx (contains --index -- $path $fish_complete_path) + set -e fish_complete_path[$idx] + end + end + # Insert the paths before $__fish_data_dir. + if set -l idx (contains --index -- $__fish_data_dir/completions $fish_complete_path) + set -l new_path $fish_complete_path[1..$idx] + set -e new_path[$idx] + set -a new_path $__extra_completionsdir + set fish_complete_path $new_path $fish_complete_path[$idx..-1] + else + set -a fish_complete_path $__extra_completionsdir + end + + ### Source conf directories ### + # The built-in directories were already sourced during shell initialization. + # Any __extra_confdir that came from $__fish_data_dir/__fish_build_paths.fish was also sourced. + # As explained above, we're using the presence of pre-existing paths in $fish_function_path as a + # signal that the corresponding conf dir has also already been sourced. + # In order to simulate this, we'll run through the same algorithm as found in + # $__fish_data_dir/config.fish except we'll avoid sourcing the file if it comes from an + # already-sourced location. + # Caveats: + # * Files will be sourced in a different order than we'd ideally do (because we're coming in + # after the fact to source them). + # * If there are existing extra conf paths, files in them may have been sourced that should have + # been suppressed by paths we're inserting in front. + # * Similarly any files in $__fish_data_dir/vendor_conf.d that should have been suppressed won't + # have been. + set -l sourcelist + for file in $__fish_config_dir/conf.d/*.fish $__fish_sysconf_dir/conf.d/*.fish + # We know these paths were sourced already. Just record them. + set -l basename (string replace -r '^.*/' '' -- $file) + contains -- $basename $sourcelist + or set -a sourcelist $basename + end + for root in $__extra_confdir + for file in $root/*.fish + set -l basename (string replace -r '^.*/' '' -- $file) + contains -- $basename $sourcelist + and continue + set -a sourcelist $basename + contains -- $root $existing_conf_paths + and continue # this is a pre-existing path, it will have been sourced already + [ -f $file -a -r $file ] + and source $file + end + end +end diff --git a/users/riking/dotfiles/fish/config.fish b/users/riking/dotfiles/fish/config.fish new file mode 100644 index 000000000000..c2454762bddf --- /dev/null +++ b/users/riking/dotfiles/fish/config.fish @@ -0,0 +1,8 @@ +set -gx GOPATH "$HOME/go" +set -gx GPG_TTY (tty) +set -gx DEPOT_ROOT "$GOPATH/src/code.tvl.fyi" + +set -gx PATH '/usr/local/go/bin' "$HOME/.cargo/bin" "$HOME/.rbenv/bin" $PATH +status --is-interactive; and rbenv init - | source +source ~/.opsrc.fish # work +set -gx PATH "$HOME/go/bin" $PATH diff --git a/users/riking/dotfiles/fish/fish_variables b/users/riking/dotfiles/fish/fish_variables new file mode 100644 index 000000000000..fa8bff919f40 --- /dev/null +++ b/users/riking/dotfiles/fish/fish_variables @@ -0,0 +1,32 @@ +# This file contains fish universal variable definitions. +# VERSION: 3.0 +SETUVAR __fish_initialized:3100 +SETUVAR fish_color_autosuggestion:555\x1ebrblack +SETUVAR fish_color_cancel:\x2dr +SETUVAR fish_color_command:005fd7 +SETUVAR fish_color_comment:990000 +SETUVAR fish_color_cwd:green +SETUVAR fish_color_cwd_root:red +SETUVAR fish_color_end:009900 +SETUVAR fish_color_error:ff0000 +SETUVAR fish_color_escape:00a6b2 +SETUVAR fish_color_history_current:\x2d\x2dbold +SETUVAR fish_color_host:normal +SETUVAR fish_color_host_remote:yellow +SETUVAR fish_color_match:\x2d\x2dbackground\x3dbrblue +SETUVAR fish_color_normal:normal +SETUVAR fish_color_operator:00a6b2 +SETUVAR fish_color_param:00afff +SETUVAR fish_color_quote:999900 +SETUVAR fish_color_redirection:00afff +SETUVAR fish_color_search_match:bryellow\x1e\x2d\x2dbackground\x3dbrblack +SETUVAR fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack +SETUVAR fish_color_status:red +SETUVAR fish_color_user:brgreen +SETUVAR fish_color_valid_path:\x2d\x2dunderline +SETUVAR fish_greeting:Welcome\x20to\x20fish\x2c\x20the\x20friendly\x20interactive\x20shell\x0aType\x20\x60help\x60\x20for\x20instructions\x20on\x20how\x20to\x20use\x20fish +SETUVAR fish_key_bindings:fish_default_key_bindings +SETUVAR fish_pager_color_completion:\x1d +SETUVAR fish_pager_color_description:B3A06D\x1eyellow +SETUVAR fish_pager_color_prefix:white\x1e\x2d\x2dbold\x1e\x2d\x2dunderline +SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan diff --git a/users/riking/dotfiles/fish/functions/ddate.fish b/users/riking/dotfiles/fish/functions/ddate.fish new file mode 100644 index 000000000000..8152d31680e7 --- /dev/null +++ b/users/riking/dotfiles/fish/functions/ddate.fish @@ -0,0 +1,3 @@ +function ddate --description 'current date in Discourse format' + TZ=UTC date '+[date=%Y-%m-%d time=%H:%M:%S timezone=\"%Z\"]' +end diff --git a/users/riking/dotfiles/fish/functions/gh-clone.fish b/users/riking/dotfiles/fish/functions/gh-clone.fish new file mode 100644 index 000000000000..109ec353f6db --- /dev/null +++ b/users/riking/dotfiles/fish/functions/gh-clone.fish @@ -0,0 +1,18 @@ +function gh-clone --description 'Clone and CD to a github repository' + if test (count $argv) -eq 1 + set user (string split "/" -- $argv[1])[1] + set repo (string split "/" -- $argv[1])[2] + else + set user $argv[1] + set repo $argv[2] + end + + if test -d "$HOME/go/src/github.com/$user/$repo" + cd "$HOME/go/src/github.com/$user/$repo" + return 0 + end + mkdir -p "$HOME/go/src/github.com/$user" + cd "$HOME/go/src/github.com/$user" + git clone "git@github.com:$user/$repo.git" + cd "$HOME/go/src/github.com/$user/$repo" +end diff --git a/users/riking/dotfiles/fish/functions/prodaccess.fish b/users/riking/dotfiles/fish/functions/prodaccess.fish new file mode 100644 index 000000000000..876c14c5e31e --- /dev/null +++ b/users/riking/dotfiles/fish/functions/prodaccess.fish @@ -0,0 +1,6 @@ +function prodaccess + ssh-add "$HOME/.ssh/id_ecdsa_sk" + begin; ssh-add -L | grep -q 'ZgEu6S3SLatYN'; end || ssh-add "$HOME"/.ssh/id_ed25519 + begin; ssh-add -L | grep -q 'Gfh2S3kUwZ8A6'; end || ssh-add "$HOME"/.ssh/id_rsa.discourse + echo "signing test" | gpg --clearsign > /dev/null +end diff --git a/users/riking/dotfiles/fish/functions/reset-audio.fish b/users/riking/dotfiles/fish/functions/reset-audio.fish new file mode 100644 index 000000000000..eb48578a52bc --- /dev/null +++ b/users/riking/dotfiles/fish/functions/reset-audio.fish @@ -0,0 +1,4 @@ +function reset-audio --description "Resets pulse and alsa" + pulseaudio -k + sudo alsa force-reload +end diff --git a/users/riking/dotfiles/fish/functions/tvl-push.fish b/users/riking/dotfiles/fish/functions/tvl-push.fish new file mode 100644 index 000000000000..f04ac830c005 --- /dev/null +++ b/users/riking/dotfiles/fish/functions/tvl-push.fish @@ -0,0 +1,3 @@ +function tvl-push + git push origin HEAD:refs/for/canon +end diff --git a/users/riking/dotfiles/regolith/Xresources b/users/riking/dotfiles/regolith/Xresources new file mode 100644 index 000000000000..f47b93511af0 --- /dev/null +++ b/users/riking/dotfiles/regolith/Xresources @@ -0,0 +1,5 @@ +#include "/etc/regolith/styles/ubuntu/root" + +i3-wm.program.lock: xset s activate +i3-wm.program.1: /bin/sh $HOME/.config/regolith/initrc + diff --git a/users/riking/dotfiles/regolith/flags/first-time-setup-r1-4-1 b/users/riking/dotfiles/regolith/flags/first-time-setup-r1-4-1 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/riking/dotfiles/regolith/flags/first-time-setup-r1-4-1 diff --git a/users/riking/dotfiles/regolith/flags/show-shortcuts b/users/riking/dotfiles/regolith/flags/show-shortcuts new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/riking/dotfiles/regolith/flags/show-shortcuts diff --git a/users/riking/dotfiles/regolith/flags/term-profile b/users/riking/dotfiles/regolith/flags/term-profile new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/riking/dotfiles/regolith/flags/term-profile diff --git a/users/riking/dotfiles/regolith/flags/ui-fingerprint b/users/riking/dotfiles/regolith/flags/ui-fingerprint new file mode 100644 index 000000000000..b35aedd2dc0f --- /dev/null +++ b/users/riking/dotfiles/regolith/flags/ui-fingerprint @@ -0,0 +1 @@ +ec33ee15ff705ac4b167ba6b7f6df3c2 diff --git a/users/riking/dotfiles/regolith/initrc b/users/riking/dotfiles/regolith/initrc new file mode 100755 index 000000000000..9b14613cd4eb --- /dev/null +++ b/users/riking/dotfiles/regolith/initrc @@ -0,0 +1,3 @@ + +xset s 900 5 +( xss-lock -n /usr/lib/xsecurelock/dimmer -l -- sh -c "XSECURELOCK_PASSWORD_PROMPT=time_hex XSECURELOCK_SHOW_DATETIME=1 XSECURELOCK_SAVER=saver_mpv XSECURELOCK_IMAGE_DURATION_SECONDS=10 XSECURELOCK_LIST_VIDEOS_COMMAND='find ~/Videos/Screensaver -type f' xsecurelock" )& diff --git a/users/riking/dotfiles/tmux.conf b/users/riking/dotfiles/tmux.conf new file mode 100644 index 000000000000..1f253cb27f0c --- /dev/null +++ b/users/riking/dotfiles/tmux.conf @@ -0,0 +1,6 @@ + +set -g mouse on +set-option -g prefix C-a +bind-key C-a send-prefix +bind | split-window -h +bind - split-window -v diff --git a/users/riking/keys.nix b/users/riking/keys.nix new file mode 100644 index 000000000000..6dd2ff18a30f --- /dev/null +++ b/users/riking/keys.nix @@ -0,0 +1,20 @@ +# SSH public keys +{ ... }: + +rec { + sk-ecljg09 = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBBwJ7dJJUkvIK+bDsVsCsCZSlbs90aOLsHN7XesC8/AmLA5rIRLO8I5ADoOjsWAXl/WAgxqOMmB4LxZjoXWa1a0AAAAEc3NoOg== riking@sk-ECLJG09"; + sk-portable1 = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBCfA8/0nKk4jXclWHjRZIuicPeyIo9oDwahpnWjEATr7YaFDAo632KTSgqlW0lpx8lX9alLsJRhFV2XaSurYw/EAAAAEc3NoOg== riking@sk-portable1"; + sk-portable2 = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBEX3DXreQR93SR68QZHTdaVd5RjlRM8C0jcZ4kI4OZwqk7xuk68w3g22q2OM7O+chj+n1N3u0hLxi82QfRnwyasAAAAEc3NoOg== riking@sk-portable2"; + sk-desktop = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBB+JvN8nAxD+yo49Ohf/UDq7Z049yvkURJIA1XNbvKaAkvfWnCN5m9vTC1FyGxTyCwy4QpD1pFP5fIn0X/kvvfgAAAAEc3NoOg== riking@sk-kane-DAN-A4"; + + u2f = [sk-ecljg09 sk-portable1 sk-portable2 sk-desktop]; + + ed1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAjWIfFH2bAWMZG+HudV1MVHWUl83M/ZgEu6S3SLatYN riking@kane-DAN-A4"; + ed2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICBblB4C9IgAijv+qN6Zs8TM2Sz7phQvVmRrcDn4VYNo riking@ECLJG09"; + + passworded = [ed1 ed2]; + + unprotected = []; + + all = u2f ++ passworded ++ unprotected; +} diff --git a/users/sterni/OWNERS b/users/sterni/OWNERS new file mode 100644 index 000000000000..cace4d0f3759 --- /dev/null +++ b/users/sterni/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - sterni diff --git a/users/sterni/clhs-lookup/README.md b/users/sterni/clhs-lookup/README.md new file mode 100644 index 000000000000..1f42ff43a210 --- /dev/null +++ b/users/sterni/clhs-lookup/README.md @@ -0,0 +1,13 @@ +# clhs-lookup + +Simple cli to lookup symbols' documentation in a local copy of the +Common Lisp HyperSpec. + +## usage + +``` +clhs-lookup [--print] symbol [symbol [...]] + + --print Print documentation paths to stdout instead of + opening them with $BROWSER (defaults to xdg-open). +``` diff --git a/users/sterni/clhs-lookup/clhs-lookup.lisp b/users/sterni/clhs-lookup/clhs-lookup.lisp new file mode 100644 index 000000000000..0e61dd901f93 --- /dev/null +++ b/users/sterni/clhs-lookup/clhs-lookup.lisp @@ -0,0 +1,46 @@ +(in-package :clhs-lookup) +(declaim (optimize (safety 3))) + +(defun find-symbols-paths (syms clhs) + "Find pathnames to HyperSpec files describing the listed + symbol names (as strings). Paths are returned in the order + of the symbols given with missing entries removed." + (check-type syms list) + (check-type clhs pathname) + (let* ((data-dir (merge-pathnames "HyperSpec/Data/" clhs)) + (data (merge-pathnames "Map_Sym.txt" data-dir)) + (found (make-hash-table :test #'equal)) + (syms (mapcar #'string-upcase syms))) + (with-open-file (s data :direction :input) + (loop + with missing = syms + for symbol-line = (read-line s nil :eof) + for path-line = (read-line s nil :eof) + until (or (eq symbol-line :eof) + (eq path-line :eof) + (null missing)) + for pos = (position symbol-line missing :test #'equal) + when pos + do (progn + (delete symbol-line missing) + (setf (gethash symbol-line found) path-line))) + ; TODO(sterni): get rid of Data/../ in path + (mapcar + (lambda (x) (merge-pathnames x data-dir)) + (remove nil + (mapcar (lambda (x) (gethash x found)) syms)))))) + +(defun main () + (let* ((browser (or (uiop:getenvp "BROWSER") "xdg-open")) + (args (uiop:command-line-arguments)) + (prin (member "--print" args :test #'equal)) + (syms (remove-if (lambda (x) (eq (char x 0) #\-)) args)) + (paths (find-symbols-paths syms *clhs-path*))) + (if (null paths) + (uiop:quit 1) + (dolist (p paths) + (if prin + (format t "~A~%" p) + (uiop:launch-program + (format nil "~A ~A" browser p) + :force-shell t)))))) diff --git a/users/sterni/clhs-lookup/default.nix b/users/sterni/clhs-lookup/default.nix new file mode 100644 index 000000000000..b6a0bd06790f --- /dev/null +++ b/users/sterni/clhs-lookup/default.nix @@ -0,0 +1,39 @@ +{ pkgs, depot, ... }: + +let + inherit (pkgs) fetchzip writeText; + inherit (depot.nix) buildLisp; + inherit (builtins) replaceStrings; + + clhsVersion = "7-0"; + + clhs = fetchzip { + name = "HyperSpec-${replaceStrings [ "-" ] [ "." ] clhsVersion}"; + url = "ftp://ftp.lispworks.com/pub/software_tools/reference/HyperSpec-${clhsVersion}.tar.gz"; + sha256 = "1zsi35245m5sfb862ibzy0pzlph48wvlggnqanymhgqkpa1v20ak"; + stripRoot = false; + }; + + clhs-path = writeText "clhs-path.lisp" '' + (in-package :clhs-lookup.clhs-path) + (defparameter *clhs-path* (pathname "${clhs}/")) + ''; + + clhs-lookup = buildLisp.program { + name = "clhs-lookup"; + + deps = [ + { + default = buildLisp.bundled "asdf"; + sbcl = buildLisp.bundled "uiop"; + } + ]; + + srcs = [ + ./packages.lisp + clhs-path + ./clhs-lookup.lisp + ]; + }; +in + clhs-lookup diff --git a/users/sterni/clhs-lookup/packages.lisp b/users/sterni/clhs-lookup/packages.lisp new file mode 100644 index 000000000000..d059b96ce9f0 --- /dev/null +++ b/users/sterni/clhs-lookup/packages.lisp @@ -0,0 +1,10 @@ +(defpackage :clhs-lookup.clhs-path + (:use :cl) + (:export :*clhs-path*)) + +(defpackage clhs-lookup + (:use :cl :uiop) + (:import-from :clhs-lookup.clhs-path :*clhs-path*) + (:export :main + :find-symbols-paths)) + diff --git a/users/sterni/dot-time-man-pages/OWNERS b/users/sterni/dot-time-man-pages/OWNERS new file mode 100644 index 000000000000..980c17b424f2 --- /dev/null +++ b/users/sterni/dot-time-man-pages/OWNERS @@ -0,0 +1,3 @@ +inherited: true +owners: + - edef diff --git a/users/sterni/dot-time-man-pages/default.nix b/users/sterni/dot-time-man-pages/default.nix new file mode 100644 index 000000000000..bf7d63dbd797 --- /dev/null +++ b/users/sterni/dot-time-man-pages/default.nix @@ -0,0 +1,70 @@ +{ depot, lib, ... }: + +let + # TODO(sterni): find a better place for this: is dot time //fun? + + # get the email address of a depot user from //ops/users + findEmail = user: + let + res = builtins.filter ({ username, ... }: username == user) depot.ops.users; + len = builtins.length res; + in + if len == 1 + then (builtins.head res).email + else builtins.throw "findEmail: got ${toString len} results instead of 1"; + + # dot-time(7) man page, ported from dotti.me + dot-time = rec { + name = "dot-time"; + section = 7; + content = '' + .Dd $Mdocdate$ + .Dt ${lib.toUpper name} ${toString section} + .Os + .Sh NAME + .Nm ${name} + .Nd a universal convention for conveying time + .Sh DESCRIPTION + For those of us who travel often or coordinate across many timezones, + working with local time is frequently impractical. + ISO8601, in all its wisdom, allows for time zone designators, + but still represents the hours and minutes as local time, + thus making it inconvenient for quickly comparing timestamps from + different locations. + .Pp + Dot time instead uses UTC for all date, hour, and minute indications, + and while it allows for time zone designators, they are optional + information that can be dropped without changing the indicated time. + It uses an alternate hour separator to make it easy to distinguish from + regular ISO8601. + When a time zone designator is provided, one can easily obtain + the matching local time by adding the UTC offset to the UTC time. + .Sh EXAMPLES + These timestamps all represent the same point in time. + .TS + allbox tab(|); + lb | lb | lb + l | l | l. + dot time|ISO8601|RFC3339 + 2019-06-19T22ยท13-04|2019-06-19T18:13-04|2019-06-19T18:13:00-04:00 + 2019-06-19T22ยท13+00|2019-06-19T22:13+00|2019-06-19T22:13:00Z + 2019-06-19T22ยท13+02|2019-06-20T00:13+02|2019-06-20T00:13:00+02:00 + .TE + .Sh SEE ALSO + .Lk https://dotti.me dotti.me + .Sh AUTHORS + .An -nosplit + .Sy dot time + has been proposed and documented by + .An edef Aq Mt ${findEmail "edef"} + and ported to + .Xr mdoc 7 + by + .An sterni Aq Mt ${findEmail "sterni"} . + ''; + }; + +in + depot.nix.buildManPages "dot-time" {} [ + dot-time + ] diff --git a/users/sterni/emacs/default.nix b/users/sterni/emacs/default.nix new file mode 100644 index 000000000000..f7bdf21a025e --- /dev/null +++ b/users/sterni/emacs/default.nix @@ -0,0 +1,43 @@ +{ depot, pkgs, ... }: + +let + inherit (pkgs.emacsGcc.pkgs) withPackages; + + emacs = withPackages (epkgs: [ + # basic setup + epkgs.elpaPackages.undo-tree + epkgs.melpaPackages.evil + epkgs.melpaPackages.evil-collection + epkgs.melpaPackages.use-package + # languages + epkgs.bqn-mode + epkgs.elpaPackages.ada-mode + epkgs.melpaPackages.adoc-mode + epkgs.melpaPackages.dockerfile-mode + epkgs.melpaPackages.haskell-mode + epkgs.melpaPackages.jq-mode + epkgs.melpaPackages.markdown-mode + epkgs.melpaPackages.nix-mode + epkgs.melpaPackages.sly + epkgs.melpaPackages.yaml-mode + epkgs.rust-mode + epkgs.urweb-mode + # misc + epkgs.melpaPackages.hl-todo + epkgs.elpaPackages.rainbow-mode + epkgs.melpaPackages.rainbow-delimiters + # beyond text editing + epkgs.melpaPackages.elfeed + epkgs.melpaPackages.magit + epkgs.tvlPackages.tvl + ]); +in + +# sadly we can't give an init-file via the command line +pkgs.writeShellScriptBin "emacs" '' + exec ${emacs}/bin/emacs \ + --no-init-file \ + --directory ${./.} \ + --eval "(require 'init)" \ + "$@" +'' diff --git a/users/sterni/emacs/init.el b/users/sterni/emacs/init.el new file mode 100644 index 000000000000..4b868cb242d1 --- /dev/null +++ b/users/sterni/emacs/init.el @@ -0,0 +1,184 @@ +;; Set default font and fallback font via set-fontset-font +;; TODO(sterni): Investigate non-emoji representation of some glyphs +(let ((mono-font "Bitstream Vera Sans Mono-12") + (emoji-font "Noto Color Emoji-12")) + (setq default-frame-alist `((font . ,mono-font))) + (set-frame-font mono-font t t) + (set-fontset-font t nil emoji-font)) + +(setq inhibit-startup-message t + display-time-24hr-format t + select-enable-clipboard t) + +;; Reload files +(global-auto-revert-mode 1) + +;; Indent +(set-default 'indent-tabs-mode nil) +(setq tab-width 2) + +;; UTF-8 +(setq locale-coding-system 'utf-8) +(set-terminal-coding-system 'utf-8) +(set-keyboard-coding-system 'utf-8) +(set-selection-coding-system 'utf-8) +(prefer-coding-system 'utf-8) + +;; Disable unnecessary GUI elements +(scroll-bar-mode 0) +(menu-bar-mode 0) +(tool-bar-mode 0) + +(add-hook 'after-make-frame-functions + (lambda (frame) (scroll-bar-mode 0))) + +;; don't center on cursor when scrolling +(setq scroll-conservatively 1) + +;; type less +(defalias 'yes-or-no-p 'y-or-n-p) + +;; Extra settings when graphical session +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b"))) + (mouse-wheel-mode t) + (blink-cursor-mode -1)) + +;; TODO(sterni): prevent some remaining backup files +(setq auto-save-file-name-transforms + `((".*" ,temporary-file-directory t))) +(setq backup-directory-alist + `((".*" . ,temporary-file-directory))) + +;; buffers +;; unique component should come first for better completion +(setq uniquify-buffer-name-style 'forward) + +;; Display column numbers +(column-number-mode t) +(setq-default fill-column 80) +(setq display-fill-column-indicator-column t) +(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode) + +;; whitespace +(setq whitespace-style '(face trailing tabs) + whitespace-line-column fill-column) +(add-hook 'prog-mode-hook #'whitespace-mode) + +;;; Configure built in modes + +;; Perl +(setq perl-indent-level 2) +(setq perl-continued-statement-offset 0) +(setq perl-continued-brace-offset 0) + +;;; Configure packages +(require 'use-package) + +(package-initialize) + +(use-package undo-tree + :config + (global-undo-tree-mode)) + +(use-package magit + :after evil + :config + ; reset (buffer-local) fill-column value to emacs' default + ; gerrit doesn't like 80 column commit messagesโฆ + (add-hook 'git-commit-mode-hook (lambda () (setq fill-column 72))) + (evil-define-key 'normal 'global (kbd "<leader>gr") 'magit-status)) +(use-package tvl :after magit) + +(setq ediff-split-window-function 'split-window-horizontally) + +(use-package evil + :init + (setq evil-want-integration t) + (setq evil-want-keybinding nil) + (setq evil-shift-width 2) + (setq evil-split-window-below t) + (setq evil-split-window-right t) + (setq evil-undo-system 'undo-tree) + :config + (evil-mode 1) + (evil-set-leader 'normal ",") ;; TODO(sterni): space would be nice, butโฆ + (evil-set-leader 'visual ",") + ;; buffer management + (evil-define-key 'normal 'global (kbd "<leader>bk") 'kill-buffer) + (evil-define-key 'normal 'global (kbd "<leader>bb") 'switch-to-buffer) + ;; window management + (evil-define-key 'normal 'global (kbd "<leader>wk") 'delete-window) + (evil-define-key 'normal 'global (kbd "<leader>wo") 'delete-other-window) + (evil-define-key 'normal 'global (kbd "<leader>wh") 'split-window-below) + (evil-define-key 'normal 'global (kbd "<leader>wv") 'split-window-right) + (evil-define-key 'normal 'global (kbd "<leader>ww") 'other-window) + ;; emacs + (evil-define-key 'visual 'global (kbd "<leader>ee") 'eval-region) + (evil-define-key 'normal 'global (kbd "<leader>ee") 'eval-last-sexp) + (evil-define-key 'normal 'global (kbd "<leader>ep") 'eval-print-last-sexp) + (evil-define-key 'normal 'global (kbd "<leader>eh") 'help) + ;; modify what is displayed + (evil-define-key 'normal 'global (kbd "<leader>dw") + (lambda () + (interactive) + (whitespace-mode 'toggle) + (display-fill-column-indicator-mode 'toggle))) + ;; elfeed bindings for evil (can't use-package elfeed apparently) + (evil-define-key 'normal 'global (kbd "<leader>ff") 'elfeed) + (evil-define-key '(normal visual) elfeed-search-mode-map + (kbd "o") 'elfeed-search-browse-url + (kbd "r") 'elfeed-search-untag-all-unread + (kbd "u") 'elfeed-search-tag-all-unread + (kbd "<leader>ff") 'elfeed-search-fetch + (kbd "<leader>fc") 'elfeed-db-compact + (kbd "<leader>fr") 'elfeed-search-update--force)) + +(use-package evil-collection + :after evil + :config + (evil-collection-init)) + +(use-package rainbow-delimiters + :hook (prog-mode . rainbow-delimiters-mode)) + +(use-package nix-mode :mode "\\.nix\\'") +(use-package nix-drv-mode :mode "\\.drv\\'") + +(use-package haskell-mode) +(use-package urweb-mode) +(use-package bqn-mode + :mode "\\.bqn\\'" + :custom bqn-mode-map-prefix "C-s-") ; probably rather using C-\ +(use-package yaml-mode) +(use-package dockerfile-mode) +(use-package jq-mode + :config (add-to-list 'auto-mode-alist '("\\.jq\\'" . jq-mode))) +(use-package rust-mode) +(use-package sly + :after evil + :hook ((sly-mrepl-mode . (lambda () (rainbow-delimiters-mode-enable)))) + :config + (evil-define-key 'normal sly-mrepl-mode-map (kbd "C-r") 'isearch-backward)) + +(use-package ada-mode) + +(use-package rainbow-mode) +(use-package hl-todo + :hook ((prog-mode . hl-todo-mode)) + :config + (setq hl-todo-keyword-faces + '(("TODO" . "#FF0000") + ("FIXME" . "#FF0000") + ("HACK" . "#7f7f7f") + ("XXX" . "#aa0000")))) + +(use-package markdown-mode + :commands (markdown-mode gfm-mode) + :mode (("\\.md\\'" . markdown-mode))) +(use-package adoc-mode + :mode (("\\.adoc\\'" . adoc-mode))) + +(require 'subscriptions) + +(provide 'init) diff --git a/users/sterni/emacs/subscriptions.el b/users/sterni/emacs/subscriptions.el new file mode 100644 index 000000000000..bf890a5ab8b3 --- /dev/null +++ b/users/sterni/emacs/subscriptions.el @@ -0,0 +1,88 @@ +;;; elfeed subscriptions + +(setq elfeed-feeds + (append + ;; immutable subscriptions tracked in git + '(("https://repology.org/maintainer/sternenseemann%40systemli.org/feed-for-repo/nix_unstable/atom" dashboard releases) + ("http://hundimbuero.blogspot.com/feeds/posts/default?alt=rss" blog cool-and-nice) + ("gopher://text.causal.agency/0feed.atom" blog) + ("http://xsteadfastx.org/feed/" blog cool-and-nice) + ("https://tvl.fyi/feed.atom" blog cool-and-nice) + ("https://hannes.robur.coop/atom" blog) + ("https://stevelosh.com/rss.xml" blog) + ("https://planet.lisp.org/rss20.xml" blog) + ("https://hyperthings.garden/rss/all-posts.xml" blog) + ("https://blog.benjojo.co.uk/rss.xml" blog) + ("https://leahneukirchen.org/blog/index.atom" blog cool-and-nice) + ("https://leahneukirchen.org/trivium/index.atom" blog links cool-and-nice) + ("https://firefly.nu/feeds/all.atom.xml" blog cool-and-nice) + ("https://tazj.in/feed.atom" blog cool-and-nice) + ("https://alyssa.is/feed.xml" blog cool-and-nice) + ("https://eta.st/feed.xml" blog cool-and-nice) + ("https://h.eta.st/rss" noisy cool-and-nice) + ("https://spectrum-os.org/git/www/atom/bibliography.html" links blog) + ("https://rachelbythebay.com/w/atom.xml" blog) + ("http://evrl.com/feed.xml" blog) + ("https://vulns.xyz/feed.xml" blog) + ("https://www.german-foreign-policy.com/?type=9818" news) + ("https://niedzejkob.p4.team/rss.xml" blog) + ("https://grahamc.com/feed/" blog) + ("https://michael.stapelberg.ch/feed.xml" blog) + ("https://kazu-yamamoto.hatenablog.jp/feed" blog) + ("https://ariadne.space/feed/" blog) + ("https://bodil.lol/rss.xml" blog) + ("http://blog.nullspace.io/feed.xml" blog) + ("https://blog.kingcons.io/rss.xml" blog) + ("http://jaspervdj.be/rss.xml" blog) + ("https://christine.website/blog.rss" blog) + ("https://drewdevault.com/feed.xml" blog) + ("https://www.imperialviolet.org/iv-rss.xml" blog) + ("https://latacora.micro.blog/feed.xml" blog) + ("https://22gato.tumblr.com/rss" pictures cool-and-nice) + ("https://theprofoundprogrammer.com/rss" blog) + ("https://wiki.openlab-augsburg.de/_feed" openlab) + ("http://shitopenlabsays.tumblr.com/rss" openlab) + ("http://suckless.org/atom.xml" releases) + ("https://kristaps.bsd.lv/lowdown/atom.xml" releases) + ("https://www.tweag.io/rss.xml" blog) + ("http://planet.haskell.org/atom.xml" planet blog) + ("http://0pointer.net/blog/index.atom" blog) + ("https://emacsninja.com/feed.atom" blog) + ("https://emacshorrors.com/feed.atom" blog) + ("http://therealmntmn.tumblr.com/rss" blog) + ("http://blog.duangle.com/feeds/posts/default" blog) + ("http://blog.johl.io/atom.xml" blog) + ("http://blog.z3bra.org/rss/feed.xml" blog) + ("http://ccc.de/de/rss/updates.xml" news) + ;; ("http://fabienne.us/feed/" blog) ; database error + ("http://feeds.feedburner.com/baschtcom" blog) + ("http://ffaaaffaffaffaa.tumblr.com/rss" pictures) + ("http://fnordig.de/feed.xml" blog) + ("http://fotografiona.tumblr.com/rss" pictures) + ("https://grandhotel-cosmopolis.org/de/feed" news) + ("http://guteaussicht.org/rss" pictures) + ("http://konvergenzfehler.de/feed/" blog) + ("https://markuscisler.com/feed.xml" blog) + ("http://n00bcore.de/feed/" podcast) + ("http://spacethatneverwas.tumblr.com/rss" pictures) + ("http://theresa.someserver.de/blog/?feed=rss2" blog) + ("http://www.frumble.de/blog/feed/" blog) + ("http://www.plomlompom.de/PlomRogue/plomwiki.php?action=Blog_Atom" blog) + ("http://www.whvrt.de/rss" pictures) + ("http://www.windytan.com/feeds/posts/default" blog) + ("https://echtsuppe.wordpress.com/feed/" blog defunct) + ("https://mgsloan.com/feed.xml" blog) + ("https://notes.sterni.lv/atom.xml" me) + ("http://arduina.net/feed/" defunct blog) + ("https://anchor.fm/s/94bb000/podcast/rss" podcast)) + ;; http://www.wollenzin.de/feed/ ;_; + + ;; add more feeds from an untracked file in $HOME + (let ((file (concat (getenv "HOME") + "/.config/emacs-custom/mutable-subscriptions.el"))) + (when (file-exists-p file) + (read (with-temp-buffer + (insert-file-contents file) + (buffer-string))))))) + +(provide 'subscriptions) diff --git a/users/sterni/exercises/aoc/.gitignore b/users/sterni/exercises/aoc/.gitignore new file mode 100644 index 000000000000..de53cfc531cb --- /dev/null +++ b/users/sterni/exercises/aoc/.gitignore @@ -0,0 +1 @@ +/*/input \ No newline at end of file diff --git a/users/sterni/exercises/aoc/2021/solutions.bqn b/users/sterni/exercises/aoc/2021/solutions.bqn new file mode 100755 index 000000000000..7aac53b9eaa7 --- /dev/null +++ b/users/sterni/exercises/aoc/2021/solutions.bqn @@ -0,0 +1,188 @@ +#!/usr/bin/env BQN + +# +# Utilities +# + +IsAsciiNum โ ('0'โธโคโงโคโ'9') + +ReadInt โ {(๐จโธร+โฃ)ยดโโฝ-โ'0'๐ฉ} # stolen from leah2 +ReadDec โ 10โธReadInt + +ReadInput โ {โขfile.Lines โพ โขpathโฟ"/input/day"โฟ(โขFmt ๐ฉ)} + +SplitOn โ ((โข (-1ห)โโฃยจ +`)โ=โโข) + +_fix โ {๐ฉ ๐โโขโโข ๐ฝ ๐ฉ} + +# +# 2021-12-01 +# + +# part 1 + +day1ExampleData โ 199โฟ200โฟ208โฟ210โฟ200โฟ207โฟ240โฟ269โฟ260โฟ263 +day1Input โ ReadDecยจReadInput 1 + +# NB: Because distance from the ground is never smaller than zero, it's +# no problem that nudge inserts a zero at the end of the right list +PositiveDeltaCount โ +ยดโ(โข<ยซ)+หหโโ + +! 7 = 1 PositiveDeltaCount day1ExampleData + +โขOut "Day 1.1: "โพโขFmt 1 PositiveDeltaCount day1Input + +# part 2 + +! 5 = 3 PositiveDeltaCount day1ExampleData + +โขOut "Day 1.2: "โพโขFmt 3 PositiveDeltaCount day1Input + +# +# 2021-12-02 +# + +# part 1 + +day2ExampleData โ โจ + "forward 5", + "down 5", + "forward 8", + "up 3", + "down 8", + "forward 2", +โฉ + +day2Input โ ReadInput 2 + +ParseSubmarineCommand โ (((โ2)โธ((((-1)โธโ)โ(2โธ|))ร(=โ(โโ(รทโ2))))โ("duf"โธโ)โโ)รReadDecโ(IsAsciiNum/โข)) + +SubmarineDestProduct โ {รยด+ยดParseSubmarineCommandยจ๐ฉ} + +! 150 = SubmarineDestProduct day2ExampleData + +โขOut "Day 2.1: "โพโขFmt SubmarineDestProduct day2Input + +# part 2 + +SubmarineAimedDestProduct โ { + รยด+ยด((รยด)โ(1โธโ)โ(1โธโ))ยจ (<0โฟ0โฟ0) (โขโพ((โโโฝโฃ)+(โโข)))` ParseSubmarineCommandยจ๐ฉ +} + +! 900 = SubmarineAimedDestProduct day2ExampleData + +โขOut "Day 2.2: "โพโขFmt SubmarineAimedDestProduct day2Input + +# +# 2021-12-03 +# + +BinTable โ '0'-ห> + +day3ExampleData โ BinTable โจ + "00100", + "11110", + "10110", + "10111", + "10101", + "01111", + "00111", + "11100", + "10000", + "11001", + "00010", + "01010", +โฉ + +day3Input โ BinTable ReadInput 3 + +DeBinList โ ((2โธร)+โฃ)ยดโฝ +_tableAggr โ {((รทโ2)โ(/โโฅ)ยดโโฝโโข๐ฝ(+ห))๐ฉ} +GammaRate โ < _tableAggr + +! 22 = DeBinList GammaRate day3ExampleData +! 9 = DeBinList ยฌGammaRate day3ExampleData + +โขOut "Day 3.1: "โพโขFmt (ยฌรโDeBinListโข) GammaRate day3Input + +_lifeSupportRating โ { + # Need to rename the arguments, otherwise the ternary expr becomes a function + bitPos โ ๐จ + Cmp โ ๐ฝ + + crit โ Cmp _tableAggr ๐ฉ + matchPos โ bitPos โห crit ((โฅหโโข)=โข) ๐ฉ + match โ matchPos/๐ฉ + {1=โ match?โmatch;(bitPos+1) Cmp _lifeSupportRating match} +} + +OxygenGeneratorRating โ DeBinList 0 โค_lifeSupportRating โข +CO2ScrubberRating โ DebinList 0 >_lifeSupportRating โข + +! 23 = OxygenGeneratorRating day3ExampleData +! 10 = CO2ScrubberRating day3ExampleData + +โขOut "Day 3.2: "โพโขFmt (OxygenGeneratorRatingรCO2ScrubberRating) day3Input + +# +# 2021-12-07 +# + +# part 1 + +day6ExampleData โ โจ16,1,2,0,4,2,7,1,2,14โฉ +day6Input โ ReadDecยจ ',' SplitOn โReadInput 6 + +PossiblePositions โ (โยด+โ(โ1โธ+)โยด) +FuelConsumption โ +หโ|โ(-โ) +_lowestFuelPossible โ {โยดโ(๐ฝโPossiblePositions)ห ๐ฉ} + +! 37 = FuelConsumption _lowestFuelPossible day6ExampleData + +โขOut "Day 7.1: "โพโขFmt FuelConsumption _lowestFuelPossible day6Input + +# part 2 + +TriNum โ 1โธ+รรทโ2 + +FuelConsumption2 โ +หโ(TriNumยจ)โ|โ(-โ) + +! 168 = FuelConsumption2 _lowestFuelPossible day6ExampleData + +โขOut "Day 7.2: "โพโขFmt FuelConsumption2 _lowestFuelPossible day6Input + +# +# 2021-12-09 +# + +# part 1 + +ParseHeightMap โ ((โ โ(โ โ))โฅโพ)โ-โ'0' + +day9ExampleData โ ParseHeightMap โจ + "2199943210", + "3987894921", + "9856789892", + "8767896789", + "9899965678" +โฉ +day9Input โ ParseHeightMap ReadInput 9 + +Rotate โ (โโฝ)โโขโโฃ # counter clockwise +LowPoints โ {โงยด๐ฉโธ(โฃ<((-โข) Rotate โโธยปหโRotateห))ยจ โ4} + +RiskLevelSum โ (+ยดโฅ)โ(1โธ+รLowPoints) + +! 15 = RiskLevelSum day9ExampleData + +โขOut "Day 9.1: "โพโขFmt RiskLevelSum day9Input + +# part 2 + +NumberBasins โ ((1โธ+โโพโฅ)รโข)โLowPoints +Basins โ {๐ฉโธ((<โ9โฃ)โง(ยซโยปโยซหโยปหโโข)โโข) _fix NumberBasins ๐ฉ} +LargestBasinsProduct โ {รยด 3โ โจ 1โ โ ยจ โโฅBasins ๐ฉ} + +! 1134 = LargestBasinsProduct day9ExampleData + +โขOut "Day 9.2: "โพโขFmt LargestBasinsProduct day9Input diff --git a/users/sterni/htmlman/README.md b/users/sterni/htmlman/README.md new file mode 100644 index 000000000000..258233d4c4d2 --- /dev/null +++ b/users/sterni/htmlman/README.md @@ -0,0 +1,36 @@ +# htmlman + +static site generator for man pages intended for +rendering man page documentation viewable using +a web browser. + +## usage + +If you have a nix expression, `doc.nix`, like this: + +```nix +{ depot, ... }: + +depot.users.sterni.htmlman { + title = "foo project"; + pages = [ + { + name = "foo"; + section = 1; + } + { + name = "foo"; + section = 3; + path = ../devman/foo.3; + } + ]; + manDir = ../man; +} +``` + +You can run the following to directly deploy the resulting +documentation output to a specific target directory: + +```sh +nix-build -A deploy doc.nix && ./result target_directory +``` diff --git a/users/sterni/htmlman/default.nix b/users/sterni/htmlman/default.nix new file mode 100644 index 000000000000..b88bc264103b --- /dev/null +++ b/users/sterni/htmlman/default.nix @@ -0,0 +1,234 @@ +{ depot, lib, pkgs, ... }: + +let + inherit (depot.nix) + getBins + runExecline + yants + ; + + inherit (depot.tools) + cheddar + ; + + inherit (pkgs) + mandoc + coreutils + fetchurl + writers + ; + + bins = getBins cheddar [ "cheddar" ] + // getBins mandoc [ "mandoc" ] + // getBins coreutils [ "cat" "mv" "mkdir" ] + ; + + normalizeDrv = fetchurl { + url = "https://necolas.github.io/normalize.css/8.0.1/normalize.css"; + sha256 = "04jmvybwh2ks4dlnfa70sb3a3z3ig4cv0ya9rizjvm140xq1h22q"; + }; + + execlineStdoutInto = target: line: [ + "redirfd" "-w" "1" target + ] ++ line; + + # I will not write a pure nix markdown renderer + # I will not write a pure nix markdown renderer + # I will not write a pure nix markdown renderer + # I will not write a pure nix markdown renderer + # I will not write a pure nix markdown renderer + markdown = md: + let + html = runExecline.local "rendered-markdown" { + stdin = md; + } ([ + "importas" "-iu" "out" "out" + ] ++ execlineStdoutInto "$out" [ + bins.cheddar "--about-filter" "description.md" + ]); + in builtins.readFile html; + + indexTemplate = { title, description, pages ? [] }: '' + <!doctype html> + <html> + <head> + <meta charset="utf-8"> + <title>${title}</title> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="index-text"> + <h1>${title}</h1> + ${markdown description} + <h2>man pages</h2> + <ul> + ${lib.concatMapStrings ({ name, section, ... }: '' + <li><a href="${name}.${toString section}.html">${name}(${toString section})</a></li> + '') pages} + </ul> + </div> + </body> + </html> + ''; + + defaultStyle = import ./defaultStyle.nix { }; + + # This deploy script automatically copies the build result into + # a TARGET directory and marks it as writeable optionally. + # It is exposed as the deploy attribute of the result of + # htmlman, so an htmlman expression can be used like this: + # nix-build -A deploy htmlman.nix && ./result target_dir + deployScript = title: drv: writers.writeDash "deploy-${title}" '' + usage() { + printf 'Usage: %s [-w] TARGET\n\n' "$0" + printf 'Deploy htmlman documentation to TARGET directory.\n\n' + printf ' -h Display this help message\n' + printf ' -w Make TARGET directory writeable\n' + } + + if test "$#" -lt 1; then + usage + exit 100 + fi + + writeable=false + + while test "$#" -gt 0; do + case "$1" in + -h) + usage + exit 0 + ;; + -w) + writeable=true + ;; + -*) + usage + exit 100 + ;; + *) + if test -z "$target"; then + target="$1" + else + echo "Too many arguments" + exit 100 + fi + ;; + esac + + shift + done + + if test -z "$target"; then + echo "Missing TARGET" + usage + exit 100 + fi + + set -ex + + mkdir -p "$target" + cp -RTL --reflink=auto "${drv}" "$target" + + if $writeable; then + chmod -R +w "$target" + fi + ''; + + htmlman = + { title + # title of the index page + , description ? "" + # description which is displayed after + # the main heading on the index page + , pages ? [] + # man pages of the following structure: + # { + # name : string; + # section : int; + # path : either path string; + # } + # path is optional, if it is not given, + # the man page source must be located at + # "${manDir}/${name}.${toString section}" + , manDir ? null + # directory in which man page sources are located + , style ? defaultStyle + # CSS to use as a string + , normalizeCss ? true + # whether to include normalize.css before the custom CSS + , linkXr ? "all" + # How to handle cross references in the html output: + # + # * none: don't convert cross references into hyperlinks + # * all: link all cross references as if they were + # rendered into $out by htmlman + # * inManDir: link to all man pages which have their source + # in `manDir` and use the format string defined + # in linkXrFallback for all other cross references. + , linkXrFallback ? "https://manpages.debian.org/unstable/%N.%S.en.html" + # fallback link to use if linkXr == "inManDir" and the man + # page is not in ${manDir}. Placeholders %N (name of page) + # and %S (section of page) can be used. See mandoc(1) for + # more information. + }: + + let + linkXrEnum = yants.enum "linkXr" [ "all" "inManDir" "none" ]; + + index = indexTemplate { + inherit title description pages; + }; + + resolvePath = { path ? null, name, section }: + if path != null + then path + else "${manDir}/${name}.${toString section}"; + + mandocOpts = lib.concatStringsSep "," ([ + "style=style.css" + ] ++ linkXrEnum.match linkXr { + all = [ "man=./%N.%S.html" ]; + inManDir = [ "man=./%N.%S.html;${linkXrFallback}" ]; + none = [ ]; + }); + + html = + runExecline.local "htmlman-${title}" { + derivationArgs = { + inherit index style; + passAsFile = [ "index" "style" ]; + }; + } ([ + "multisubstitute" [ + "importas" "-iu" "out" "out" + "importas" "-iu" "index" "indexPath" + "importas" "-iu" "style" "stylePath" + ] + "if" [ bins.mkdir "-p" "$out" ] + "if" [ bins.mv "$index" "\${out}/index.html" ] + "if" (execlineStdoutInto "\${out}/style.css" [ + "if" ([ + bins.cat + ] ++ lib.optional normalizeCss normalizeDrv + ++ [ + "$style" + ]) + ]) + # let mandoc check for available man pages + "execline-cd" "${manDir}" + ] ++ lib.concatMap ({ name, section, ... }@p: + execlineStdoutInto "\${out}/${name}.${toString section}.html" [ + "if" [ + bins.mandoc + "-mdoc" + "-T" "html" + "-O" mandocOpts + (resolvePath p) + ] + ]) pages); + in html // { + deploy = deployScript title html; + }; +in + htmlman diff --git a/users/sterni/htmlman/defaultStyle.nix b/users/sterni/htmlman/defaultStyle.nix new file mode 100644 index 000000000000..a44b5ef06934 --- /dev/null +++ b/users/sterni/htmlman/defaultStyle.nix @@ -0,0 +1,49 @@ +{ ... }: + +'' + body { + font-size: 1em; + line-height: 1.5; + font-family: serif; + background-color: #efefef; + } + + h1, h2, h3, h4, h5, h6 { + font-family: sans-serif; + font-size: 1em; + margin: 5px 0; + } + + h1 { + margin-top: 0; + } + + a:link, a:visited { + color: #3e7eff; + } + + h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + text-decoration: none; + } + + .manual-text, .index-text { + padding: 20px; + max-width: 800px; + background-color: white; + margin: 0 auto; + } + + table.head, table.foot { + display: none; + } + + .Nd { + display: inline; + } + + /* use same as cheddar for man pages */ + pre { + padding: 16px; + background-color: #f6f8fa; + } +'' diff --git a/users/sterni/keys.nix b/users/sterni/keys.nix new file mode 100644 index 000000000000..815f62ee080e --- /dev/null +++ b/users/sterni/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + all = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk+KvgvI2oJTppMASNUfMcMkA2G5ZNt+HnWDzaXKLlo lukas@wolfgang" + ]; +} diff --git a/users/sterni/mblog/cli.lisp b/users/sterni/mblog/cli.lisp new file mode 100644 index 000000000000..93be7e8b8e44 --- /dev/null +++ b/users/sterni/mblog/cli.lisp @@ -0,0 +1,17 @@ +(in-package :mblog) +(declaim (optimize (safety 3))) + +(defparameter +synopsis+ "mnote-html FILE [FILE [ ... ]]") + +;; TODO(sterni): handle relevant conditions +(defun main () + (let* ((args (uiop:command-line-arguments)) + (help-p (or (not args) + (find-if (lambda (x) + (member x '("-h" "--help" "--usage") + :test #'string=)) + args)))) + (if help-p (format *error-output* "Usage: ~A~%" +synopsis+) + (loop for arg in args + do (apple-note-html-fragment + (mime:mime-message (pathname arg)) *standard-output*))))) diff --git a/users/sterni/mblog/default.nix b/users/sterni/mblog/default.nix new file mode 100644 index 000000000000..16ae573ba78c --- /dev/null +++ b/users/sterni/mblog/default.nix @@ -0,0 +1,31 @@ +{ depot, pkgs, ... }: + +depot.nix.buildLisp.program { + name = "mnote-html"; + + srcs = [ + ./packages.lisp + ./transformer.lisp + ./note.lisp + ./cli.lisp + ]; + + deps = [ + { + sbcl = depot.nix.buildLisp.bundled "uiop"; + default = depot.nix.buildLisp.bundled "asdf"; + } + depot.third_party.lisp.alexandria + depot.third_party.lisp.closure-html + depot.third_party.lisp.cl-who + depot.third_party.lisp.mime4cl + ]; + + main = "mblog:main"; + + # due to sclf + brokenOn = [ + "ccl" + "ecl" + ]; +} diff --git a/users/sterni/mblog/note.lisp b/users/sterni/mblog/note.lisp new file mode 100644 index 000000000000..fa4de0956ffb --- /dev/null +++ b/users/sterni/mblog/note.lisp @@ -0,0 +1,60 @@ +(in-package :mblog) +(declaim (optimize (safety 3))) + +;;; util + +(defun html-escape-stream (in out) + "Escape characters read from stream IN and write them to + stream OUT escaped using WHO:ESCAPE-CHAR-MINIMAL." + (loop for char = (read-char in nil nil) + while char + do (write-string (who:escape-char-minimal char) out))) + +(defun cid-header-value (cid) + "Takes a Content-ID as present in Apple Notes' <object> tags and properly + surrounds them with angle brackets for a MIME header" + (concatenate 'string "<" cid ">")) + +;;; main implementation + +;; TODO(sterni): make this a โparserโ instead of a predicate +(defun apple-note-p (msg) + "Checks X-Uniform-Type-Identifier of a MIME:MIME-MESSAGE + to determine if a given mime message is an Apple Note." + (when-let (uniform-id (assoc "X-Uniform-Type-Identifier" + (mime:mime-message-headers msg) + :test #'string=)) + (string= (cdr uniform-id) "com.apple.mail-note"))) + +(defun apple-note-html-fragment (msg out) + "Takes a MIME:MIME-MESSAGE and writes its text content as HTML to + the OUT stream. The <object> tags are resolved to <img> which + refer to the respective attachment's filename as a relative path, + but extraction of the attachments must be done separately. The + surrounding <html> and <body> tags are stripped and <head> + discarded completely, so only a fragment which can be included + in custom templates remains." + (let ((text (find-mime-text-part msg))) + (cond + ;; Sanity checking of the note + ((not (apple-note-p msg)) + (error "Unsupported or missing X-Uniform-Type-Identifier")) + ((not text) (error "Malformed Apple Note: no text part")) + ;; notemap creates text/plain notes we need to handle properly. + ;; Additionally we *could* check X-Mailer which notemap sets + ((string= (mime:mime-subtype text) "plain") + (html-escape-stream (mime:mime-body-stream text :binary nil) out)) + ;; Notes.app creates text/html parts + ((string= (mime:mime-subtype text) "html") + (closure-html:parse + (mime:mime-body-stream text) + (make-instance + 'apple-note-transformer + :cid-lookup + (lambda (cid) + (when-let* ((part (mime:find-mime-part-by-id msg (cid-header-value cid))) + (file (mime:mime-part-file-name part))) + file)) + :next-handler + (closure-html:make-character-stream-sink out)))) + (t (error "Malformed Apple Note: unknown mime type"))))) diff --git a/users/sterni/mblog/packages.lisp b/users/sterni/mblog/packages.lisp new file mode 100644 index 000000000000..ca2e41b6827f --- /dev/null +++ b/users/sterni/mblog/packages.lisp @@ -0,0 +1,15 @@ +(defpackage :mblog + (:use + :common-lisp + :mime4cl + :closure-html + :who + :uiop) + (:shadow :with-html-output) ; conflict between closure-html and who + (:import-from + :alexandria + :when-let* + :when-let + :starts-with-subseq + :ends-with-subseq) + (:export :main)) diff --git a/users/sterni/mblog/transformer.lisp b/users/sterni/mblog/transformer.lisp new file mode 100644 index 000000000000..f26c5652a266 --- /dev/null +++ b/users/sterni/mblog/transformer.lisp @@ -0,0 +1,127 @@ +(in-package :mblog) +(declaim (optimize (safety 3))) + +;; Throw away these tags and all of their children +(defparameter +discard-tags-with-children+ '("HEAD")) +;; Only โstripโ these tags and leave their content as is +(defparameter +discard-tags-only+ '("BODY" "HTML")) + +;; This is basically the same as cxml's PROXY-HANDLER. +;; Couldn't be bothered to make a BROADCAST-HANDLER because I +;; only need to pass through to one handler. It accepts every +;; event and passes it on to NEXT-HANDLER. This is useful for +;; subclassing mostly where an event can be modified or passed +;; on as is via CALL-NEXT-METHOD. +(defclass hax-proxy-handler (hax:default-handler) + ((next-handler + :initarg :next-handler + :accessor proxy-next-handler))) + +;; Define the trivial handlers which just call themselves for NEXT-HANDLER +(macrolet ((def-proxy-handler (name (&rest args)) + `(defmethod ,name ((h hax-proxy-handler) ,@args) + (,name (proxy-next-handler h) ,@args)))) + (def-proxy-handler hax:start-document (name p-id s-id)) + (def-proxy-handler hax:end-document ()) + (def-proxy-handler hax:start-element (name attrs)) + (def-proxy-handler hax:end-element (name)) + (def-proxy-handler hax:characters (data)) + (def-proxy-handler hax:unescaped (data)) + (def-proxy-handler hax:comment (data))) + +(defclass apple-note-transformer (hax-proxy-handler) + ((cid-lookup + :initarg :cid-lookup + :initform (lambda (cid) nil) + :accessor transformer-cid-lookup) + (discard-until + :initarg :discard-until + :initform nil + :accessor transformer-discard-until) + (depth + :initarg :depth + :initform 0 + :accessor transformer-depth)) + (:documentation + "HAX handler that strips unnecessary tags from the HTML of a com.apple.mail-note + and resolves references to attachments to IMG tags.")) + +;; Define the โboringโ handlers which just call the next method (i. e. the next +;; handler) unless discard-until is not nil in which case the event is dropped. +(macrolet ((def-filter-handler (name (&rest args)) + `(defmethod ,name ((h apple-note-transformer) ,@args) + (when (not (transformer-discard-until h)) + (call-next-method))))) + (def-filter-handler hax:start-document (name p-id s-id)) + (def-filter-handler hax:end-document ()) + (def-filter-handler hax:characters (data)) + (def-filter-handler hax:unescaped (data)) + (def-filter-handler hax:comment (data))) + +(defun parse-content-id (attrlist) + (when-let (data (find-if (lambda (x) + (string= (hax:attribute-name x) "DATA")) + attrlist)) + (multiple-value-bind (starts-with-cid-p suffix) + (starts-with-subseq "cid:" (hax:attribute-value data) + :return-suffix t :test #'char=) + (if starts-with-cid-p suffix data)))) + +(defmethod hax:start-element ((handler apple-note-transformer) name attrs) + (with-accessors ((discard-until transformer-discard-until) + (next-handler proxy-next-handler) + (cid-lookup transformer-cid-lookup) + (depth transformer-depth)) + handler + + (cond + ;; If we are discarding, any started element is dropped, + ;; since the end-condition only is reached via END-ELEMENT. + (discard-until nil) + ;; If we are not discarding any outer elements, we can set + ;; up a new discard condition if we encounter an appropriate + ;; element. + ((member name +discard-tags-with-children+ :test #'string=) + (setf discard-until (cons name depth))) + ;; Only drop this event, must be mirrored in END-ELEMENT to + ;; avoid invalidly nested HTML. + ((member name +discard-tags-only+ :test #'string=) nil) + ;; If we encounter an object tag, we drop it and its contents, + ;; but only after inspecting its attributes and emitting new + ;; events representing an img tag which includes the respective + ;; attachment via its filename. + ((string= name "OBJECT") + (progn + (setf discard-until (cons "OBJECT" depth)) + ;; TODO(sterni): check type and only resolve images, raise error + ;; otherwise. We should only encounter images anyways, since + ;; other types are only supported for iCloud which doesn't seem + ;; to use IMAP for sync these days. + (when-let* ((cid (parse-content-id attrs)) + (file (apply cid-lookup (list cid))) + (src (hax:make-attribute "SRC" file))) + (hax:start-element next-handler "IMG" (list src)) + (hax:end-element next-handler "IMG")))) + ;; In all other cases, we use HAX-PROXY-HANDLER to pass the event on. + (t (call-next-method))) + (setf depth (1+ depth)))) + +(defmethod hax:end-element ((handler apple-note-transformer) name) + (with-accessors ((discard-until transformer-discard-until) + (depth transformer-depth)) + handler + + (setf depth (1- depth)) + (cond + ;; If we are discarding and encounter the same tag again at the same + ;; depth, we can stop, but still have to discard the current tag. + ((and discard-until + (string= (car discard-until) name) + (= (cdr discard-until) depth)) + (setf discard-until nil)) + ;; In all other cases, we drop properly. + (discard-until nil) + ;; Mirrored tag stripping as in START-ELEMENT + ((member name +discard-tags-only+ :test #'string=) nil) + ;; In all other cases, we use HAX-PROXY-HANDLER to pass the event on. + (t (call-next-method))))) diff --git a/users/sterni/nix/char/all-chars.bin b/users/sterni/nix/char/all-chars.bin new file mode 100644 index 000000000000..017b909e8e8e --- /dev/null +++ b/users/sterni/nix/char/all-chars.bin @@ -0,0 +1,2 @@ + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛ \ No newline at end of file diff --git a/users/sterni/nix/char/default.nix b/users/sterni/nix/char/default.nix new file mode 100644 index 000000000000..aacfc9dcbe4d --- /dev/null +++ b/users/sterni/nix/char/default.nix @@ -0,0 +1,95 @@ +{ depot, lib, pkgs, ... }: + +let + + inherit (depot.users.sterni.nix.flow) + cond + ; + + inherit (depot.nix) + yants + ; + + inherit (depot.users.sterni.nix) + string + ; + + # A char is the atomic element of a nix string + # which is essentially an array of arbitrary bytes + # as long as they are not a NUL byte. + # + # A char is neither a byte nor a unicode codepoint! + char = yants.restrict "char" (s: builtins.stringLength s == 1) yants.string; + + # integer representation of char + charval = yants.restrict "charval" (i: i >= 1 && i < 256) yants.int; + + allChars = builtins.readFile ./all-chars.bin; + + # Originally I searched a list for this, but came to the + # conclusion that this can never be fast enough in Nix. + # We therefore use a solution similar to infinisil's. + ordMap = builtins.listToAttrs + (lib.imap1 (i: v: { name = v; value = i; }) + (string.toChars allChars)); + + # Note on performance: + # chr and ord have been benchmarked using the following cases: + # + # builtins.map ord (lib.stringToCharacters allChars) + # builtins.map chr (builtins.genList (int.add 1) 255 + # + # The findings are as follows: + # 1. Searching through either strings using recursion is + # unbearably slow in Nix, leading to evaluation times + # of up to 3s for the following very small test case. + # This is why we use the trusty attribute set for ord. + # 2. String indexing is much faster than list indexing which + # is why we use the former for chr. + ord = c: ordMap."${c}"; + + chr = i: string.charAt (i - 1) allChars; + + asciiAlpha = c: + let + v = ord c; + in (v >= 65 && v <= 90) + || (v >= 97 && v <= 122); + + asciiNum = c: + let + v = ord c; + in v >= 48 && v <= 57; + + asciiAlphaNum = c: asciiAlpha c || asciiNum c; + +in { + inherit + allChars + char + charval + ord + chr + asciiAlpha + asciiNum + asciiAlphaNum + ; + + # originally I generated a nix file containing a list of + # characters, but infinisil uses a better way which I adapt + # which is using builtins.readFile instead of import. + __generateAllChars = pkgs.runCommandCC "generate-all-chars" { + source = '' + #include <stdio.h> + + int main(void) { + for(int i = 1; i <= 0xff; i++) { + putchar(i); + } + } + ''; + passAsFile = [ "source" ]; + } '' + $CC -o "$out" -x c "$sourcePath" + ''; +} diff --git a/users/sterni/nix/char/tests/default.nix b/users/sterni/nix/char/tests/default.nix new file mode 100644 index 000000000000..49b439adbb84 --- /dev/null +++ b/users/sterni/nix/char/tests/default.nix @@ -0,0 +1,31 @@ +{ depot, ... }: + +let + inherit (depot.nix.runTestsuite) + it + assertEq + runTestsuite + ; + + inherit (depot.users.sterni.nix) + char + string + int + fun + ; + + charList = string.toChars char.allChars; + + testAllCharConversion = it "tests conversion of all chars" [ + (assertEq "char.chr converts to char.allChars" + (builtins.genList (fun.rl char.chr (int.add 1)) 255) + charList) + (assertEq "char.ord converts from char.allChars" + (builtins.genList (int.add 1) 255) + (builtins.map char.ord charList)) + ]; + +in + runTestsuite "char" [ + testAllCharConversion + ] diff --git a/users/sterni/nix/flow/default.nix b/users/sterni/nix/flow/default.nix new file mode 100644 index 000000000000..b5783bd86deb --- /dev/null +++ b/users/sterni/nix/flow/default.nix @@ -0,0 +1,82 @@ +{ depot, ... }: + +let + + inherit (depot.nix) + yants + ; + + inherit (depot.users.sterni.nix) + fun + ; + + # we must avoid evaluating any of the sublists + # as they may contain conditions that throw + condition = yants.restrict "condition" + (ls: builtins.length ls == 2) + (yants.list yants.any); + + /* Like the common lisp macro: takes a list + of two elemented lists whose first element + is a boolean. The second element of the + first list that has true as its first + element is returned. + + Type: [ [ bool a ] ] -> a + + Example: + + cond [ + [ (builtins.isString true) 12 ] + [ (3 == 2) 13 ] + [ true 42 ] + ] + + => 42 + */ + cond = conds: switch true conds; + + /* Generic pattern match-ish construct for nix. + Takes a bunch of lists which are of length + two and checks the first element for either + a predicate or a value. The second value of + the first list which either has a value equal + to or a function that evaluates to true for + the given value. + + Type: a -> [ [ (function | a) b ] ] -> b + + Example: + + switch "foo" [ + [ "smol" "SMOL!!!" ] + [ (x: builtins.stringLength x <= 3) "smol-ish" ] + [ (fun.const true) "not smol" ] + ] + + => "smol-ish" + */ + switch = x: conds: + if builtins.length conds == 0 + then builtins.throw "exhausted all conditions" + else + let + c = condition (builtins.head conds); + s = builtins.head c; + b = + if builtins.isFunction s + then s x + else x == s; + in + if b + then builtins.elemAt c 1 + else switch x (builtins.tail conds); + + + +in { + inherit + cond + switch + ; +} diff --git a/users/sterni/nix/flow/tests/default.nix b/users/sterni/nix/flow/tests/default.nix new file mode 100644 index 000000000000..54cea01858e7 --- /dev/null +++ b/users/sterni/nix/flow/tests/default.nix @@ -0,0 +1,39 @@ +{ depot, ... }: + +let + + inherit (depot.nix.runTestsuite) + runTestsuite + it + assertEq + assertThrows + ; + + inherit (depot.users.sterni.nix.flow) + cond + match + ; + + dontEval = builtins.throw "this should not get evaluated"; + + testCond = it "tests cond" [ + (assertThrows "malformed cond list" + (cond [ [ true 1 2 ] [ false 1 ] ])) + (assertEq "last is true" "last" + (cond [ + [ false dontEval] + [ false dontEval ] + [ true "last" ] + ])) + (assertEq "first is true" 1 + (cond [ + [ true 1 ] + [ true dontEval ] + [ true dontEval ] + ])) + ]; + +in + runTestsuite "nix.flow" [ + testCond + ] diff --git a/users/sterni/nix/fun/default.nix b/users/sterni/nix/fun/default.nix new file mode 100644 index 000000000000..6b3541ed4c65 --- /dev/null +++ b/users/sterni/nix/fun/default.nix @@ -0,0 +1,59 @@ +{ depot, lib, ... }: + +let + + inherit (lib) + id + ; + + # Simple function composition, + # application is right to left. + rl = f1: f2: + (x: f1 (f2 x)); + + # Compose a list of functions, + # application is right to left. + rls = fs: + builtins.foldl' (fOut: f: lr f fOut) id fs; + + # Simple function composition, + # application is left to right. + lr = f1: f2: + (x: f2 (f1 x)); + + # Compose a list of functions, + # application is left to right + lrs = x: fs: + builtins.foldl' (v: f: f v) x fs; + + # Warning: cursed function + # + # Check if a function has an attribute + # set pattern with an ellipsis as its argument. + # + # s/o to puck for discovering that you could use + # builtins.toXML to introspect functions more than + # you should be able to in Nix. + hasEllipsis = f: + builtins.isFunction f && + builtins.match ".*<attrspat ellipsis=\"1\">.*" + (builtins.toXML f) != null; + +in + +{ + inherit (lib) + fix + flip + const + ; + + inherit + id + rl + rls + lr + lrs + hasEllipsis + ; +} diff --git a/users/sterni/nix/fun/tests/default.nix b/users/sterni/nix/fun/tests/default.nix new file mode 100644 index 000000000000..6492554306e1 --- /dev/null +++ b/users/sterni/nix/fun/tests/default.nix @@ -0,0 +1,29 @@ +{ depot, ... }: + +let + inherit (depot.nix.runTestsuite) + runTestsuite + it + assertEq + ; + + inherit (depot.users.sterni.nix) + fun + ; + + hasEllipsisTests = it "checks fun.hasEllipsis" [ + (assertEq "Malicious string" false + (fun.hasEllipsis (builtins.toXML ({ foo, ... }: 12)))) + (assertEq "No function" false + (fun.hasEllipsis 23)) + (assertEq "No attribute set pattern" false + (fun.hasEllipsis (a: a + 2))) + (assertEq "No ellipsis" false + (fun.hasEllipsis ({ foo, bar }: foo + bar))) + (assertEq "Ellipsis" true + (fun.hasEllipsis ({ depot, pkgs, ... }: 42))) + ]; +in + runTestsuite "nix.fun" [ + hasEllipsisTests + ] diff --git a/users/sterni/nix/html/README.md b/users/sterni/nix/html/README.md new file mode 100644 index 000000000000..0349e466a166 --- /dev/null +++ b/users/sterni/nix/html/README.md @@ -0,0 +1,148 @@ +# html.nix โ _the_ most cursed Nix HTML DSL + +A quick example to show you what it looks like: + +```nix +# Note: this example is for standalone usage out of depot +{ pkgs ? import <nixpkgs> {} }: + +let + # zero dependency, one file implementation + htmlNix = import ./path/to/html.nix { }; + + # make the magic work + inherit (htmlNix) __findFile esc withDoctype; +in + +pkgs.writeText "example.html" (withDoctype (<html> {} [ + (<head> {} [ + (<meta> { charset = "utf-8"; } null) + (<title> {} (esc "hello world")) + ]) + (<body> {} [ + (<h1> {} (esc "hello world")) + (<p> { class = "intro"; } (esc '' + welcome to the land of sillyness! + '')) + (<ul> {} [ + (<li> {} [ + (esc "check out ") + (<a> { href = "https://code.tvl.fyi"; } "depot") + ]) + (<li> {} [ + (esc "find ") + (<a> { href = "https://cl.tvl.fyi/q/hashtag:cursed"; } "cursed things") + ]) + ]) + ]) +])) +``` + +Convince yourself it works: + +```console +$ $BROWSER $(nix-build example.nix) +``` + +Alternatively, in depot: + +```console +$ $BROWSER $(nix-build -A users.sterni.nix.html.tests) +``` + +## Creating tags + +An empty tag is passed `null` as its content argument: + +```nix +<link> { + rel = "stylesheet"; + href = "/main.css"; + type = "text/css"; +} null + +# => "<link href=\"/main.css\" rel=\"stylesheet\" type=\"text/css\"/>" +``` + +Content is expected to be HTML: + +```nix +<div> { class = "foo"; } "<strong>hi</strong>" + +# => "<div class=\"foo\"><strong>hi</strong></div>" +``` + +If it's not, be sure to escape it: + +```nix +<p> {} (esc "A => B") + +# => "<p>A => B</p>" +``` + +Nesting tags works of course: + +```nix +<div> {} (<strong> {} (<em> {} "hi")) + +# => "<div><strong><em>hi</em></strong></div>" +``` + +If the content of a tag is a list, it's concatenated: + +```nix +<h1> {} [ + (esc "The ") + (<strong> {} "Nix") + (esc " ") + (<em> {} "Expression") + (esc " Language") +] + +# => "<h1>The <strong>Nix</strong> <em>Expression</em> Language</h1>" +``` + +More detailed documentation can be found in `nixdoc`-compatible +comments in the source file (`default.nix` in this directory). + +## How does this work? + +*Theoretically* expressions like `<nixpkgs>` are just ordinary paths โ +their actual value is determined from `NIX_PATH`. `html.nix` works +because of how this is actually implemented: At [parse time][spath-parsing] +Nix transparently translates an expression like `<foo>` into +`__findFile __nixPath "foo"`: + +``` +nix-repl> <nixpkgs> +/nix/var/nix/profiles/per-user/root/channels/vuizvui/nixpkgs + +nix-repl> __findFile __nixPath "nixpkgs" +/nix/var/nix/profiles/per-user/root/channels/vuizvui/nixpkgs +``` + +This translation doesn't take any scoping issues into account -- +so we can just shadow `__findFile` and make it return anything, +even a function: + +``` +nix-repl> __findFile = nixPath: str: + /**/ if str == "double" then x: x * 2 + else if str == "triple" then x: x * 3 + else throw "what?" + +nix-repl> <double> 2 +4 + +nix-repl> <triple> 3 +9 + +nix-repl> <quadruple> 4 +error: what? +``` + +Exactly this is what we are doing in `html.nix`: +Using `let inherit (htmlNix) __findFile; in` we shadow the builtin `__findFile` +with a function which returns a function rendering a particular HTML tag. + +[spath-parsing]: https://github.com/NixOS/nix/blob/293220bed5a75efc963e33c183787e87e55e28d9/src/libexpr/parser.y#L410-L416 diff --git a/users/sterni/nix/html/default.nix b/users/sterni/nix/html/default.nix new file mode 100644 index 000000000000..2498d832aadf --- /dev/null +++ b/users/sterni/nix/html/default.nix @@ -0,0 +1,119 @@ +# Copyright ยฉ 2021 sterni +# SPDX-License-Identifier: MIT +# +# This file provides a cursed HTML DSL for nix which works by overloading +# the NIX_PATH lookup operation via angle bracket operations, e. g. `<nixpkgs>`. + +{ ... }: + +let + /* Escape everything we have to escape in an HTML document if either + in a normal context or an attribute string (`<>&"'`). + + A shorthand for this function called `esc` is also provided. + + Type: string -> string + + Example: + + escapeMinimal "<hello>" + => "<hello>" + */ + escapeMinimal = builtins.replaceStrings + [ "<" ">" "&" "\"" "'" ] + [ "<" ">" "&" """ "'" ]; + + /* Return a string with a correctly rendered tag of the given name, + with the given attributes which are automatically escaped. + + If the content argument is `null`, the tag will have no children nor a + closing element. If the content argument is a string it is used as the + content as is (unescaped). If the content argument is a list, its + elements are concatenated. + + `renderTag` is only an internal function which is reexposed as `__findFile` + to allow for much neater syntax than calling `renderTag` everywhere: + + ```nix + { depot, ... }: + let + inherit (depot.users.sterni.nix.html) __findFile esc; + in + + <html> {} [ + (<head> {} (<title> {} (esc "hello world"))) + (<body> {} [ + (<h1> {} (esc "hello world")) + (<p> {} (esc "foo bar")) + ]) + ] + + ``` + + As you can see, the need to call a function disappears, instead the + `NIX_PATH` lookup operation via `<foo>` is overloaded, so it becomes + `renderTag "foo"` automatically. + + Since the content argument may contain the result of other `renderTag` + calls, we can't escape it automatically. Instead this must be done manually + using `esc`. + + Type: string -> attrs<string> -> (list<string> | string | null) -> string + + Example: + + <link> { + rel = "stylesheet"; + href = "/css/main.css"; + type = "text/css"; + } null + + renderTag "link" { + rel = "stylesheet"; + href = "/css/main.css"; + type = "text/css"; + } null + + => "<link href=\"/css/main.css\" rel=\"stylesheet\" type=\"text/css\"/>" + + <p> {} [ + "foo " + (<strong> {} "bar") + ] + + renderTag "p" {} "foo <strong>bar</strong>" + => "<p>foo <strong>bar</strong></p>" + */ + renderTag = tag: attrs: content: + let + attrs' = builtins.concatStringsSep "" ( + builtins.map (n: + " ${escapeMinimal n}=\"${escapeMinimal (toString attrs.${n})}\"" + ) (builtins.attrNames attrs) + ); + content' = + if builtins.isList content + then builtins.concatStringsSep "" content + else content; + in + if content == null + then "<${tag}${attrs'}/>" + else "<${tag}${attrs'}>${content'}</${tag}>"; + + /* Prepend "<!DOCTYPE html>" to a string. + + Type: string -> string + + Example: + + withDoctype (<body> {} (esc "hello")) + => "<!DOCTYPE html><body>hello</body>" + */ + withDoctype = doc: "<!DOCTYPE html>" + doc; + +in { + inherit escapeMinimal renderTag withDoctype; + + __findFile = _: renderTag; + esc = escapeMinimal; +} diff --git a/users/sterni/nix/html/tests/default.nix b/users/sterni/nix/html/tests/default.nix new file mode 100644 index 000000000000..8688b6937130 --- /dev/null +++ b/users/sterni/nix/html/tests/default.nix @@ -0,0 +1,84 @@ +{ depot, pkgs, ... }: + +let + inherit (depot.users.sterni.nix.html) + __findFile + esc + withDoctype + ; + + exampleDocument = withDoctype (<html> { lang = "en"; } [ + (<head> {} [ + (<meta> { charset = "utf-8"; } null) + (<title> {} "html.nix example document") + (<link> { + rel = "license"; + href = "https://code.tvl.fyi/about/LICENSE"; + type = "text/html"; + } null) + (<style> {} (esc '' + hgroup h2 { + font-weight: normal; + } + + dd { + margin: 0; + } + '')) + ]) + (<body> {} [ + (<main> {} [ + (<hgroup> {} [ + (<h1> {} (esc "html.nix")) + (<h2> {} [ + (<em> {} "the") + (esc " most cursed HTML DSL ever!") + ]) + ]) + (<dl> {} [ + (<dt> {} [ + (esc "Q: Wait, it's all ") + (<a> { + href = "https://cl.tvl.fyi/q/hashtag:cursed"; + } (esc "cursed")) + (esc " nix hacks?") + ]) + (<dd> {} (esc "A: Always has been. ๐ซ")) + (<dt> {} (esc "Q: Why does this work?")) + (<dd> {} [ + (esc "Because nix ") + (<a> { + href = "https://github.com/NixOS/nix/blob/293220bed5a75efc963e33c183787e87e55e28d9/src/libexpr/parser.y#L410-L416"; + } (esc "translates ")) + (<a> { + href = "https://github.com/NixOS/nix/blob/293220bed5a75efc963e33c183787e87e55e28d9/src/libexpr/lexer.l#L100"; + } (esc "SPATH tokens")) + (esc " like ") + (<code> {} (esc "<nixpkgs>")) + (esc " into calls to ") + (<code> {} (esc "__findFile")) + (esc " in the ") + (<em> {} (esc "current")) + (esc " scope.") + ]) + ]) + ]) + ]) + ]); +in + +pkgs.runCommandNoCC "html.nix.html" { + passAsFile = [ "exampleDocument" ]; + inherit exampleDocument; + nativeBuildInputs = [ pkgs.html5validator ]; +} '' + set -x + test "${esc "<> && \" \'"}" = "<> && " '" + + # slow as hell unfortunately + html5validator "$exampleDocumentPath" + + mv "$exampleDocumentPath" "$out" + + set +x +'' diff --git a/users/sterni/nix/int/default.nix b/users/sterni/nix/int/default.nix new file mode 100644 index 000000000000..b3157571272f --- /dev/null +++ b/users/sterni/nix/int/default.nix @@ -0,0 +1,124 @@ +{ depot, lib, ... }: + +let + + # TODO(sterni): implement nix.float and figure out which of these + # functions can be split out into a common nix.num + # library. + + inherit (depot.users.sterni.nix) + string + ; + + inherit (builtins) + bitOr + bitAnd + bitXor + mul + div + add + sub + ; + + abs = i: if i < 0 then -i else i; + + exp = base: pow: + if pow > 0 + then base * (exp base (pow - 1)) + else if pow < 0 + then 1.0 / exp base (abs pow) + else 1; + + bitShiftR = bit: count: + if count == 0 + then bit + else div (bitShiftR bit (count - 1)) 2; + + bitShiftL = bit: count: + if count == 0 + then bit + else 2 * (bitShiftL bit (count - 1)); + + hexdigits = "0123456789ABCDEF"; + + toHex = int: + let + go = i: + if i == 0 + then "" + else go (bitShiftR i 4) + + string.charAt (bitAnd i 15) hexdigits; + sign = lib.optionalString (int < 0) "-"; + in + if int == 0 + then "0" + else "${sign}${go (abs int)}"; + + fromHexMap = builtins.listToAttrs + (lib.imap0 (i: c: { name = c; value = i; }) + (lib.stringToCharacters hexdigits)); + + fromHex = literal: + let + negative = string.charAt 0 literal == "-"; + start = if negative then 1 else 0; + len = builtins.stringLength literal; + # reversed list of all digits + digits = builtins.genList + (i: string.charAt (len - 1 - i) literal) + (len - start); + parsed = builtins.foldl' + (v: d: { + val = v.val + (fromHexMap."${d}" * v.mul); + mul = v.mul * 16; + }) + { val = 0; mul = 1; } digits; + in + if negative + then -parsed.val + else parsed.val; + + # A nix integer is a 64bit signed integer + maxBound = 9223372036854775807; + + # fun fact: -9223372036854775808 is the lower bound + # for a nix integer (as you would expect), but you can't + # use it as an integer literal or you'll be greeted with: + # error: invalid integer '9223372036854775808' + # This is because all int literals when parsing are + # positive, negative "literals" are positive literals + # which are preceded by the arithmetric negation operator. + minBound = -9223372036854775807 - 1; + + odd = x: bitAnd x 1 == 1; + even = x: bitAnd x 1 == 0; + + # div and mod behave like quot and rem in Haskell, + # i. e. they truncate towards 0 + mod = a: b: let res = a / b; in a - (res * b); + + inRange = a: b: x: x >= a && x <= b; + +in { + inherit + maxBound + minBound + abs + exp + odd + even + add + sub + mul + div + mod + bitShiftR + bitShiftL + bitOr + bitAnd + bitXor + toHex + fromHex + inRange + ; +} diff --git a/users/sterni/nix/int/tests/default.nix b/users/sterni/nix/int/tests/default.nix new file mode 100644 index 000000000000..fac45dd251e1 --- /dev/null +++ b/users/sterni/nix/int/tests/default.nix @@ -0,0 +1,203 @@ +{ depot, lib, ... }: + +let + + inherit (depot.nix.runTestsuite) + runTestsuite + it + assertEq + ; + + inherit (depot.users.sterni.nix) + int + string + fun + ; + + testBounds = it "checks minBound and maxBound" [ + # this is gonna blow up in my face because + # integer overflow is undefined behavior in + # C++, so most likely anything could happen? + (assertEq "maxBound is the maxBound" true + (int.maxBound + 1 < int.maxBound)) + (assertEq "minBound is the minBound" true + (int.minBound - 1 > int.minBound)) + (assertEq "maxBound overflows to minBound" + (int.maxBound + 1) + int.minBound) + (assertEq "minBound overflows to maxBound" + (int.minBound - 1) + int.maxBound) + ]; + + expectedBytes = [ + "00" "01" "02" "03" "04" "05" "06" "07" "08" "09" "0A" "0B" "0C" "0D" "0E" "0F" + "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "1A" "1B" "1C" "1D" "1E" "1F" + "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "2A" "2B" "2C" "2D" "2E" "2F" + "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" "3A" "3B" "3C" "3D" "3E" "3F" + "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" "4A" "4B" "4C" "4D" "4E" "4F" + "50" "51" "52" "53" "54" "55" "56" "57" "58" "59" "5A" "5B" "5C" "5D" "5E" "5F" + "60" "61" "62" "63" "64" "65" "66" "67" "68" "69" "6A" "6B" "6C" "6D" "6E" "6F" + "70" "71" "72" "73" "74" "75" "76" "77" "78" "79" "7A" "7B" "7C" "7D" "7E" "7F" + "80" "81" "82" "83" "84" "85" "86" "87" "88" "89" "8A" "8B" "8C" "8D" "8E" "8F" + "90" "91" "92" "93" "94" "95" "96" "97" "98" "99" "9A" "9B" "9C" "9D" "9E" "9F" + "A0" "A1" "A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9" "AA" "AB" "AC" "AD" "AE" "AF" + "B0" "B1" "B2" "B3" "B4" "B5" "B6" "B7" "B8" "B9" "BA" "BB" "BC" "BD" "BE" "BF" + "C0" "C1" "C2" "C3" "C4" "C5" "C6" "C7" "C8" "C9" "CA" "CB" "CC" "CD" "CE" "CF" + "D0" "D1" "D2" "D3" "D4" "D5" "D6" "D7" "D8" "D9" "DA" "DB" "DC" "DD" "DE" "DF" + "E0" "E1" "E2" "E3" "E4" "E5" "E6" "E7" "E8" "E9" "EA" "EB" "EC" "ED" "EE" "EF" + "F0" "F1" "F2" "F3" "F4" "F5" "F6" "F7" "F8" "F9" "FA" "FB" "FC" "FD" "FE" "FF" + ]; + + hexByte = i: string.fit { width = 2; char = "0"; } (int.toHex i); + + hexInts = [ + { left = 0; right = "0"; } + { left = 1; right = "1"; } + { left = 11; right = "B"; } + { left = 123; right = "7B"; } + { left = 9000; right = "2328"; } + { left = 2323; right = "913"; } + { left = 4096; right = "1000"; } + { left = int.maxBound; right = "7FFFFFFFFFFFFFFF"; } + { left = int.minBound; right = "-8000000000000000"; } + ]; + + testHex = it "checks conversion to hex" (lib.flatten [ + (lib.imap0 (i: hex: [ + (assertEq "hexByte ${toString i} == ${hex}" (hexByte i) hex) + (assertEq "${toString i} == fromHex ${hex}" i (int.fromHex hex)) + ]) expectedBytes) + (builtins.map ({ left, right }: [ + (assertEq "toHex ${toString left} == ${right}" (int.toHex left) right) + (assertEq "${toString left} == fromHex ${right}" left (int.fromHex right)) + ]) hexInts) + ]); + + testBasic = it "checks basic int operations" [ + (assertEq "122 is even" (int.even 122 && !(int.odd 122)) true) + (assertEq "123 is odd" (int.odd 123 && !(int.even 123)) true) + (assertEq "abs -4959" (int.abs (-4959)) 4959) + ]; + + expNumbers = [ + { left = -3; right = 0.125; } + { left = -2; right = 0.25; } + { left = -1; right = 0.5; } + { left = 0; right = 1; } + { left = 1; right = 2; } + { left = 2; right = 4; } + { left = 3; right = 8; } + { left = 4; right = 16; } + { left = 5; right = 32; } + { left = 16; right = 65536; } + ]; + + testExp = it "checks exponentiation" + (builtins.map ({ left, right }: + assertEq + "2 ^ ${toString left} == ${toString right}" + (int.exp 2 left) right) expNumbers); + + shifts = [ + { a = 2; b = 5; c = 64; op = "<<"; } + { a = -2; b = 5; c = -64; op = "<<"; } + { a = 123; b = 4; c = 1968; op = "<<"; } + { a = 1; b = 8; c = 256; op = "<<"; } + { a = 256; b = 8; c = 1; op = ">>"; } + { a = 374; b = 2; c = 93; op = ">>"; } + { a = 2; b = 2; c = 0; op = ">>"; } + { a = 99; b = 9; c = 0; op = ">>"; } + ]; + + checkShift = { a, b, c, op }@args: + let + f = string.match op { + "<<" = int.bitShiftL; + ">>" = int.bitShiftR; + }; + in assertEq "${toString a} ${op} ${toString b} == ${toString c}" (f a b) c; + + checkShiftRDivExp = n: + assertEq "${toString n} >> 5 == ${toString n} / 2 ^ 5" + (int.bitShiftR n 5) (int.div n (int.exp 2 5)); + + checkShiftLMulExp = n: + assertEq "${toString n} >> 6 == ${toString n} * 2 ^ 6" + (int.bitShiftL n 5) (int.mul n (int.exp 2 5)); + + testBit = it "checks bitwise operations" (lib.flatten [ + (builtins.map checkShift shifts) + (builtins.map checkShiftRDivExp [ + 1 + 2 + 3 + 5 + 7 + 23 + 1623 + 238 + 34 + 348 + 2834 + 834 + 348 + ]) + (builtins.map checkShiftLMulExp [ + 1 + 2 + 3 + 5 + 7 + 23 + 384 + 3 + 2 + 5991 + 85109 + 38 + ]) + ]); + + divisions = [ + { a = 2; b = 1; c = 2; mod = 0;} + { a = 2; b = 2; c = 1; mod = 0;} + { a = 20; b = 10; c = 2; mod = 0;} + { a = 12; b = 5; c = 2; mod = 2;} + { a = 23; b = 4; c = 5; mod = 3;} + ]; + + checkDiv = n: { a, b, c, mod }: [ + (assertEq "${n}: div result" (int.div a b) c) + (assertEq "${n}: mod result" (int.mod a b) mod) + (assertEq "${n}: divMod law" ((int.div a b) * b + (int.mod a b)) a) + ]; + + testDivMod = it "checks integer division and modulo" + (lib.flatten [ + (builtins.map (checkDiv "+a / +b") divisions) + (builtins.map (fun.rl (checkDiv "-a / +b") (x: x // { + a = -x.a; + c = -x.c; + mod = -x.mod; + })) divisions) + (builtins.map (fun.rl (checkDiv "+a / -b") (x: x // { + b = -x.b; + c = -x.c; + })) divisions) + (builtins.map (fun.rl (checkDiv "-a / -b") (x: x // { + a = -x.a; + b = -x.b; + mod = -x.mod; + })) divisions) + ]); + +in + runTestsuite "nix.int" [ + testBounds + testHex + testBasic + testExp + testBit + testDivMod + ] diff --git a/users/sterni/nix/string/default.nix b/users/sterni/nix/string/default.nix new file mode 100644 index 000000000000..19d2cec243c0 --- /dev/null +++ b/users/sterni/nix/string/default.nix @@ -0,0 +1,114 @@ +{ depot, lib, ... }: + +let + + inherit (depot.users.sterni.nix.char) + chr + ord + ; + + inherit (depot.users.sterni.nix) + int + flow + ; + + take = n: s: + builtins.substring 0 n s; + + drop = n: s: + builtins.substring n int.maxBound s; + + charAt = i: s: + let + r = builtins.substring i 1 s; + in if r == "" then null else r; + + charIndex = char: s: + let + len = builtins.stringLength s; + go = i: + flow.cond [ + [ (i >= len) null ] + [ (charAt i s == char) i ] + [ true (go (i + 1)) ] + ]; + in go 0; + + toChars = lib.stringToCharacters; + fromChars = lib.concatStrings; + + toBytes = str: + builtins.map ord (toChars str); + + fromBytes = is: lib.concatMapStrings chr is; + + pad = { left ? 0, right ? 0, char ? " " }: s: + let + leftS = fromChars (builtins.genList (_: char) left); + rightS = fromChars (builtins.genList (_: char) right); + in "${leftS}${s}${rightS}"; + + fit = { char ? " ", width, side ? "left" }: s: + let + diff = width - builtins.stringLength s; + in + if diff <= 0 + then s + else pad { inherit char; "${side}" = diff; } s; + + # pattern matching for strings only + match = val: matcher: matcher."${val}"; + + /* Bare-bones printf implementation. Supported format specifiers: + + * `%%` escapes `%` + * `%s` is substituted by a string + + As expected, the first argument is a format string and the values + for its format specifiers need to provided as the next arguments + in order. + + Type: string -> (printfVal : either string (a -> printfVal)) + */ + printf = formatString: + let + specifierWithArg = token: builtins.elem token [ + "%s" + ]; + isSpecifier = lib.hasPrefix "%"; + + tokens = lib.flatten (builtins.split "(%.)" formatString); + argsNeeded = builtins.length (builtins.filter specifierWithArg tokens); + + format = args: (builtins.foldl' ({ out ? "", argIndex ? 0 }: token: { + argIndex = argIndex + (if specifierWithArg token then 1 else 0); + out = + /**/ if token == "%s" then out + builtins.elemAt args argIndex + else if token == "%%" then out + "%" + else if isSpecifier token then throw "Unsupported format specifier ${token}" + else out + token; + }) {} tokens).out; + + accumulateArgs = argCount: args: + if argCount > 0 + then arg: accumulateArgs (argCount - 1) (args ++ [ arg ]) + else format args; + in + accumulateArgs argsNeeded []; + +in { + inherit + take + drop + charAt + charIndex + toBytes + fromBytes + toChars + fromChars + pad + fit + match + printf + ; +} diff --git a/users/sterni/nix/string/tests/default.nix b/users/sterni/nix/string/tests/default.nix new file mode 100644 index 000000000000..c8aec9464077 --- /dev/null +++ b/users/sterni/nix/string/tests/default.nix @@ -0,0 +1,72 @@ +{ depot, ... }: + +let + + inherit (depot.users.sterni.nix) + string + ; + + inherit (depot.nix.runTestsuite) + it + assertEq + runTestsuite + ; + + testTakeDrop = it "tests take and drop" [ + (assertEq "take" + (string.take 5 "five and more") + "five ") + (assertEq "drop" + (string.drop 2 "coin") + "in") + (assertEq "take out of bounds" + (string.take 100 "foo") + "foo") + (assertEq "drop out of bounds" + (string.drop 42 "lol") + "") + ]; + + testIndexing = it "tests string indexing" [ + (assertEq "normal charAt" + (string.charAt 3 "helo") + "o") + (assertEq "out of bounds charAt" + (string.charAt 5 "helo") + null) + ]; + + testFinding = it "tests finding in strings" [ + (assertEq "normal charIndex" + (string.charIndex "d" "abcdefghijkl") + 3) + (assertEq "charIndex no match" + (string.charIndex "w" "zZzZzzzZZZ") + null) + ]; + + dontEval = builtins.throw "this should not get evaluated"; + + testMatch = it "tests match" [ + (assertEq "basic match usage" 42 + (string.match "answer" { + "answer" = 42; + "banana" = dontEval; + "maleur" = dontEval; + })) + ]; + + f = "f"; + testPrintf = it "prints f" [ + (assertEq "basic %s usage" "print ${f}" (string.printf "print %s" f)) + (assertEq "% escaping" "100%" (string.printf "100%%")) + ]; + +in + runTestsuite "nix.string" [ + testTakeDrop + testIndexing + testFinding + testMatch + testPrintf + ] diff --git a/users/sterni/nix/url/default.nix b/users/sterni/nix/url/default.nix new file mode 100644 index 000000000000..37bd0de66ac9 --- /dev/null +++ b/users/sterni/nix/url/default.nix @@ -0,0 +1,81 @@ +{ depot, lib, ... }: + +let + + inherit (depot.users.sterni.nix) + char + int + string + flow + ; + + reserved = c: builtins.elem c [ + "!" "#" "$" "&" "'" "(" ")" + "*" "+" "," "/" ":" ";" "=" + "?" "@" "[" "]" + ]; + + unreserved = c: char.asciiAlphaNum c + || builtins.elem c [ "-" "_" "." "~" ]; + + percentEncode = c: + if unreserved c + then c + else "%" + (string.fit { + width = 2; + char = "0"; + side = "left"; + } (int.toHex (char.ord c))); + + encode = { leaveReserved ? false }: s: + let + chars = lib.stringToCharacters s; + tr = c: + if leaveReserved && reserved c + then c + else percentEncode c; + in lib.concatStrings (builtins.map tr chars); + + decode = s: + let + tokens = builtins.split "%" s; + decodeStep = + { result ? "" + , inPercent ? false + }: s: + flow.cond [ + [ + (builtins.isList s) + { + inherit result; + inPercent = true; + } + ] + [ + inPercent + { + inPercent = false; + # first two characters came after an % + # the rest is the string until the next % + result = result + + char.chr (int.fromHex (string.take 2 s)) + + (string.drop 2 s); + } + ] + [ + (!inPercent) + { + result = result + s; + } + ] + ]; + + in + (builtins.foldl' decodeStep {} tokens).result; + +in { + inherit + encode + decode + ; +} diff --git a/users/sterni/nix/url/tests/default.nix b/users/sterni/nix/url/tests/default.nix new file mode 100644 index 000000000000..7cf53cde1555 --- /dev/null +++ b/users/sterni/nix/url/tests/default.nix @@ -0,0 +1,56 @@ +{ depot, ... }: + +let + + inherit (depot.nix.runTestsuite) + it + assertEq + runTestsuite + ; + + inherit (depot.users.sterni.nix) + url + ; + + checkEncoding = args: { left, right }: + assertEq "encode ${builtins.toJSON left} == ${builtins.toJSON right}" + (url.encode args left) right; + + checkDecoding = { left, right }: + assertEq "${builtins.toJSON left} == decode ${builtins.toJSON right}" + (url.decode left) right; + + unreserved = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~"; + + encodeExpected = [ + { left = "Laguna Beach"; right = "Laguna%20Beach"; } + { left = "๐พ Exterminate!"; right = "%F0%9F%91%BE%20Exterminate%21"; } + { left = unreserved; right = unreserved; } + { + left = "`!@#$%^&*()+={}[]:;'\\|<>,?/ \""; + right = "%60%21%40%23%24%25%5E%26%2A%28%29%2B%3D%7B%7D%5B%5D%3A%3B%27%5C%7C%3C%3E%2C%3F%2F%20%22"; + } + ]; + + testEncode = it "checks url.encode" + (builtins.map (checkEncoding {}) encodeExpected); + + testDecode = it "checks url.decode" + (builtins.map checkDecoding encodeExpected); + + testLeaveReserved = it "checks that leaveReserved is like id for valid URLs" + (builtins.map (x: checkEncoding { leaveReserved = true; } { left = x; right = x; }) [ + "ftp://ftp.is.co.za/rfc/rfc1808.txt" + "http://www.ietf.org/rfc/rfc2396.txt" + "ldap://[2001:db8::7]/c=GB?objectClass?one" + "mailto:John.Doe@example.com" + "news:comp.infosystems.www.servers.unix" + "tel:+1-816-555-1212" + "telnet://192.0.2.16:80/" + "urn:oasis:names:specification:docbook:dtd:xml:4.1.2" + ]); +in + runTestsuite "nix.url" [ + testEncode + testLeaveReserved + ] diff --git a/users/sterni/nix/utf8/default.nix b/users/sterni/nix/utf8/default.nix new file mode 100644 index 000000000000..270da934b6a6 --- /dev/null +++ b/users/sterni/nix/utf8/default.nix @@ -0,0 +1,313 @@ +{ depot, lib, ... }: + +let + + inherit (depot.users.sterni.nix) + char + flow + fun + int + string + util + ; + + /* (Internal) function to determine the amount + bytes left in a UTF-8 byte sequence from the + first byte. + + This function will throw if the given first + byte is ill-formed, but will not detect all + cases of ill-formed-ness. + + Based on table 3-6. from The Unicode Standard, + Version 13.0, section 3.9. + + Type: integer -> integer + */ + byteCount = i: flow.cond [ + [ (int.bitAnd i 128 == 0) 1 ] + [ (int.bitAnd i 224 == 192) 2 ] + [ (int.bitAnd i 240 == 224) 3 ] + [ (int.bitAnd i 248 == 240) 4 ] + [ true (builtins.throw "Ill-formed first byte ${int.toHex i}") ] + ]; + + /* (Internal) function to check if a given byte in + an UTF-8 byte sequence is well-formed. + + Based on table 3-7. from The Unicode Standard, + Version 13.0, section 3.9. + + Type: integer -> integer -> integer -> bool + */ + wellFormedByte = + # first byte's integer value + first: + # byte position as an index starting with 0 + pos: + let + defaultRange = int.inRange 128 191; + + secondBytePredicate = flow.switch first [ + [ (int.inRange 194 223) defaultRange ] # C2..DF + [ 224 (int.inRange 160 191) ] # E0 + [ (int.inRange 225 236) defaultRange ] # E1..EC + [ 237 (int.inRange 128 159) ] # ED + [ (int.inRange 238 239) defaultRange ] # EE..EF + [ 240 (int.inRange 144 191) ] # F0 + [ (int.inRange 241 243) defaultRange ] # F1..F3 + [ 244 (int.inRange 128 143) ] # F4 + [ (fun.const true) null ] + ]; + + firstBytePredicate = byte: assert first == byte; + first < 128 || secondBytePredicate != null; + in + # Either ASCII or in one of the byte ranges of Table 3-6. + if pos == 0 then firstBytePredicate + # return predicate according to Table 3-6. + else if pos == 1 then assert secondBytePredicate != null; secondBytePredicate + # 3rd and 4th byte have only one validity rule + else defaultRange; + + /* Iteration step for decoding an UTF-8 byte sequence. + It decodes incrementally, i. e. it has to be fed + one byte at a time and then returns either a + new state or a final result. + + If the resulting attribute set contains the attribute + result, it is finished and the decoded codepoint is + contained in that attribute. In all other cases, + pass the returned set to step again along with + a new byte. The initial state to pass is the empty + set. + + Extra attributes are always passed through, so you + can pass extra state. Be sure not to use result, + pos, code, first or count. + + This function will throw with a fairly detailed + message if it encounters ill-formed bytes. + + The implementation is based on The Unicode Standard, + Version 13.0, section 3.9, especially table 3-6. + + Type: { ... } -> string -> ({ result :: integer, ... } | { ... }) + + Example: utf8.step {} "f" + => { result = 102; } + */ + step = { pos ? 0, code ? 0, ... }@args: byte: + let + value = char.ord byte; + # first byte is context for well-formed-ness + first = args.first or value; + count = args.count or (byteCount first); + newCode = + if count == 1 + then int.bitAnd 127 first # ascii character + else # multi byte UTF-8 sequence + let + # Calculate the bitmask for extracting the + # codepoint data in the current byte. + # If the codepoint is not ASCII, the bits + # used for codepoint data differ depending + # on the byte position and overall byte + # count. The first byte always ignores + # the (count + 1) most significant bits. + # For all subsequent bytes, the 2 most + # significant bits need to be ignored. + # See also table 3-6. + mask = + if pos == 0 + then int.exp 2 (8 - (count + 1)) - 1 + else 63; + # UTF-8 uses the 6 least significant bits in all + # subsequent bytes after the first one. Therefore + # We can determine the amount we need to shift + # the current value by the amount of bytes left. + offset = (count - (pos + 1)) * 6; + in + code + (int.bitShiftL (int.bitAnd mask value) offset); + illFormedMsg = + "Ill-formed byte ${int.toHex value} at position ${toString pos} in ${toString count} byte UTF-8 sequence"; + in + if !(wellFormedByte first pos value) then builtins.throw illFormedMsg + else if pos + 1 == count + then (builtins.removeAttrs args [ # allow extra state being passed through + "count" + "code" + "pos" + "first" + ]) // { result = newCode; } + else (builtins.removeAttrs args [ "result" ]) // { + inherit count first; + code = newCode; + pos = pos + 1; + }; + + /* Decode an UTF-8 string into a list of codepoints. + + Throws if the string is ill-formed UTF-8. + + Type: string -> [ integer ] + */ + # TODO(sterni): option to fallback to replacement char instead of failure + decode = s: + let + stringLength = builtins.stringLength s; + iterResult = builtins.genericClosure { + startSet = [ + { + key = "start"; + stringIndex = -1; + state = {}; + codepoint = null; + } + ]; + operator = { state, stringIndex, ... }: + let + # updated values for current iteration step + newIndex = stringIndex + 1; + newState = step state (builtins.substring newIndex 1 s); + in lib.optional (newIndex < stringLength) { + # unique keys to make genericClosure happy + key = toString newIndex; + # carryover state for the next step + stringIndex = newIndex; + state = newState; + # actual payload for later, steps with value null are filtered out + codepoint = newState.result or null; + }; + }; + in + # extract all steps that yield a code point into a list + builtins.map (v: v.codepoint) ( + builtins.filter ( + { codepoint, stringIndex, state, ... }: + + let + # error message in case we are missing bytes at the end of input + earlyEndMsg = + if state ? count && state ? pos + then "Missing ${toString (with state; count - pos)} bytes at end of input" + else "Unexpected end of input"; + in + + # filter out all iteration steps without a codepoint value + codepoint != null + # if we are at the iteration step of a non-empty input string, throw + # an error if no codepoint was returned, as it indicates an incomplete + # UTF-8 sequence. + || (stringLength > 0 && stringIndex == stringLength - 1 && throw earlyEndMsg) + + ) iterResult + ); + + /* Pretty prints a Unicode codepoint in the U+<HEX> notation. + + Type: integer -> string + */ + formatCodepoint = cp: "U+" + string.fit { + width = 4; + char = "0"; + } (int.toHex cp); + + encodeCodepoint = cp: + let + # Find the amount of bytes needed to encode the given codepoint. + # Note that this doesn't check if the Unicode codepoint is allowed, + # but rather allows all theoretically UTF-8-encodeable ones. + count = flow.switch cp [ + [ (int.inRange 0 127) 1 ] # 00000000 0xxxxxxx + [ (int.inRange 128 2047) 2 ] # 00000yyy yyxxxxxx + [ (int.inRange 2048 65535) 3 ] # zzzzyyyy yyxxxxxx + [ (int.inRange 65536 1114111) 4 ] # 000uuuuu zzzzyyyy yyxxxxxx, + # capped at U+10FFFF + + [ (fun.const true) (builtins.throw invalidCodepointMsg) ] + ]; + + invalidCodepointMsg = "${formatCodepoint cp} is not a Unicode codepoint"; + + # Extract the bit ranges x, y, z and u from the given codepoint + # according to Table 3-6. from The Unicode Standard, Version 13.0, + # section 3.9. u is split into uh and ul since they are used in + # different bytes in the end. + components = lib.mapAttrs (_: { mask, offset }: + int.bitAnd (int.bitShiftR cp offset) mask + ) { + x = { + mask = if count > 1 then 63 else 127; + offset = 0; + }; + y = { + mask = if count > 2 then 63 else 31; + offset = 6; + }; + z = { + mask = 15; + offset = 12; + }; + # u which belongs into the second byte + ul = { + mask = 3; + offset = 16; + }; + # u which belongs into the first byte + uh = { + mask = 7; + offset = 18; + }; + }; + inherit (components) x y z ul uh; + + # Finally construct the byte sequence for the given codepoint. This is + # usually done by using the component and adding a few bits as a prefix + # which depends on the length of the sequence. The longer the sequence, + # the further back each component is pushed. To simplify this, we + # always construct a 4 element list and take the last `count` elements. + # Thanks to laziness the bogus values created by this are never evaluated. + # + # Based on table 3-6. from The Unicode Standard, + # Version 13.0, section 3.9. + bytes = lib.sublist (4 - count) count [ + # 11110uuu + (uh + 240) + # 10uuzzzz or 1110zzzz + (z + (if count > 3 then 128 + int.bitShiftL ul 4 else 224)) + # 10yyyyyy or 110yyyyy + (y + (if count > 2 then 128 else 192)) + # 10xxxxxx or 0xxxxxxx + (x + (if count > 1 then 128 else 0)) + ]; + + firstByte = builtins.head bytes; + + unableToEncodeMessage = "Can't encode ${formatCodepoint cp} as UTF-8"; + + in string.fromBytes ( + builtins.genList (i: + let + byte = builtins.elemAt bytes i; + in + if wellFormedByte firstByte i byte + then byte + else builtins.throw unableToEncodeMessage + ) count + ); + + /* Encode a list of Unicode codepoints into an UTF-8 string. + + Type: [ integer ] -> string + */ + encode = lib.concatMapStrings encodeCodepoint; + +in { + inherit + encode + decode + step + formatCodepoint + ; +} diff --git a/users/sterni/nix/utf8/tests/default.nix b/users/sterni/nix/utf8/tests/default.nix new file mode 100644 index 000000000000..ddcd34208a6d --- /dev/null +++ b/users/sterni/nix/utf8/tests/default.nix @@ -0,0 +1,141 @@ +{ depot, pkgs, lib, ... }: + +let + + inherit (pkgs) + runCommandLocal + ; + + inherit (depot.nix.runTestsuite) + runTestsuite + it + assertEq + assertThrows + assertDoesNotThrow + ; + + inherit (depot.nix.writers) + rustSimple + ; + + inherit (depot.users.sterni.nix) + int + utf8 + string + char + ; + + rustDecoder = rustSimple { + name = "utf8-decode"; + } '' + use std::io::{self, Read}; + fn main() -> std::io::Result<()> { + let mut buffer = String::new(); + io::stdin().read_to_string(&mut buffer)?; + + print!("[ "); + + for c in buffer.chars() { + print!("{} ", u32::from(c)); + } + + print!("]"); + + Ok(()) + } + ''; + + rustDecode = s: + let + expr = runCommandLocal "${s}-decoded" {} '' + printf '%s' ${lib.escapeShellArg s} | ${rustDecoder} > $out + ''; + in import expr; + + hexDecode = l: + utf8.decode (string.fromBytes (builtins.map int.fromHex l)); + + hexEncode = l: utf8.encode (builtins.map int.fromHex l); + + testFailures = it "checks UTF-8 decoding failures" ([ + (assertThrows "truncated UTF-8 string throws" (hexDecode [ "F0" "9F" ])) + # examples from The Unicode Standard + (assertThrows "ill-formed: C0 AF" (hexDecode [ "C0" "AF" ])) + (assertThrows "ill-formed: E0 9F 80" (hexDecode [ "E0" "9F" "80" ])) + (assertEq "well-formed: F4 80 83 92" (hexDecode [ "F4" "80" "83" "92" ]) [ 1048786 ]) + (assertThrows "Codepoint out of range: 0xFFFFFF" (hexEncode [ "FFFFFF" ])) + (assertThrows "Codepoint out of range: -0x02" (hexEncode [ "-02" ])) + ] ++ builtins.genList (i: + let + cp = i + int.fromHex "D800"; + in + assertThrows "Can't encode UTF-16 reserved characters: ${utf8.formatCodepoint cp}" + (utf8.encode [ cp ]) + ) (int.fromHex "07FF")); + + testAscii = it "checks decoding of ascii strings" + (builtins.map (s: assertEq "ASCII decoding is equal to UTF-8 decoding for \"${s}\"" + (string.toBytes s) (utf8.decode s)) [ + "foo bar" + "hello\nworld" + "carriage\r\nreturn" + "1238398494829304 []<><>({})[]!!)" + (string.take 127 char.allChars) + ]); + + randomUnicode = [ + "" # empty string should yield empty list + "๐ฅฐ๐จโ๐จโ๐งโ๐ฆ๐โโฌ๐ฉ๐ฝโ๐ฆฐ" + # https://kermitproject.org/utf8.html + "แ แแปแซแแฆแฆแซแ แฑแฉแ แขแฑแซแ แแฑแชแซแทแแปแนแฆแแณแขแ" + "An preost wes on leoden, Laศamon was ihoten" + "Sรฎne klรขwen durh die wolken sint geslagen," + "ฮคแฝด ฮณฮปแฟถฯฯฮฑ ฮผฮฟแฟฆ แผฮดฯฯฮฑฮฝ แผฮปฮปฮทฮฝฮนฮบแฝด" + "ะะฐ ะฑะตัะตะณั ะฟััััะฝะฝัั ะฒะพะปะฝ" + "แแแแฎแแก แขแงแแแกแแแ แจแแแ แ แฃแกแแแแแแ" + "เฎฏเฎพเฎฎเฎฑเฎฟเฎจเฏเฎค เฎฎเฏเฎดเฎฟเฎเฎณเฎฟเฎฒเฏ เฎคเฎฎเฎฟเฎดเฏเฎฎเฏเฎดเฎฟ เฎชเฏเฎฒเฏ เฎเฎฉเฎฟเฎคเฎพเฎตเฎคเฏ เฎเฎเฏเฎเฏเฎฎเฏ เฎเฎพเฎฃเฏเฎฎเฏ, " + "เฒฌเฒพ เฒเฒฒเณเฒฒเฒฟ เฒธเฒเฒญเฒตเฒฟเฒธเณ " + ]; + + # https://kermitproject.org/utf8.html + glassSentences = [ + "Euro Symbol: โฌ." + "Greek: ฮฯฮฟฯฯ ฮฝฮฑ ฯฮฌฯ ฯฯฮฑฯฮผฮญฮฝฮฑ ฮณฯ ฮฑฮปฮนฮฌ ฯฯฯฮฏฯ ฮฝฮฑ ฯฮฌฮธฯ ฯฮฏฯฮฟฯฮฑ." + "รslenska / Icelandic: รg get etiรฐ gler รกn รพess aรฐ meiรฐa mig." + "Polish: Mogฤ jeลฤ szkลo, i mi nie szkodzi." + "Romanian: Pot sฤ mฤnรขnc sticlฤ ศi ea nu mฤ rฤneศte." + "Ukrainian: ะฏ ะผะพะถั ัััะธ ัะบะปะพ, ะน ะฒะพะฝะพ ะผะตะฝั ะฝะต ะฟะพัะบะพะดะธัั." + "Armenian: ิฟึีถีกีด ีกีบีกีฏีซ ีธึีฟีฅีฌ ึ ีซีถีฎีซ ีกีถีฐีกีถีฃีซีฝีฟ ีนีจีถีฅึึ" + "Georgian: แแแแแก แแญแแ แแ แแ แ แแขแแแแ." + "Hindi: เคฎเฅเค เคเคพเคเค เคเคพ เคธเคเคคเคพ เคนเฅเค, เคฎเฅเคเฅ เคเคธ เคธเฅ เคเฅเค เคชเฅเคกเคพ เคจเคนเฅเค เคนเฅเคคเฅ." + "Hebrew(2): ืื ื ืืืื ืืืืื ืืืืืืช ืืื ืื ืืืืง ืื." + "Yiddish(2): ืืื ืงืขื ืขืกื ืืืึธื ืืื ืขืก ืืื ืืืจ ื ืืฉื ืฐืฒ." + "Arabic(2): ุฃูุง ูุงุฏุฑ ุนูู ุฃูู ุงูุฒุฌุงุฌ ู ูุฐุง ูุง ูุคูู ูู." + "Japanese: ็งใฏใฌใฉในใ้ฃในใใใพใใใใใฏ็งใๅทใคใใพใใใ" + "Thai: เธเธฑเธเธเธดเธเธเธฃเธฐเธเธเนเธเน เนเธเนเธกเธฑเธเนเธกเนเธเธณเนเธซเนเธเธฑเธเนเธเนเธ " + ]; + + testDecoding = it "checks decoding of UTF-8 strings against Rust's String" + (builtins.map + (s: assertEq "Decoding of โ${s}โ is correct" (utf8.decode s) (rustDecode s)) + (lib.flatten [ + glassSentences + randomUnicode + ])); + + testDecodingEncoding = it "checks that decoding and then encoding forms an identity" + (builtins.map + (s: assertEq "Decoding and then encoding โ${s}โ yields itself" + (utf8.encode (utf8.decode s)) s) + (lib.flatten [ + glassSentences + randomUnicode + ])); + +in + runTestsuite "nix.utf8" [ + testFailures + testAscii + testDecoding + testDecodingEncoding + ] diff --git a/users/sterni/nixpkgs-crate-holes/default.nix b/users/sterni/nixpkgs-crate-holes/default.nix new file mode 100644 index 000000000000..a022568dc941 --- /dev/null +++ b/users/sterni/nixpkgs-crate-holes/default.nix @@ -0,0 +1,284 @@ +{ depot, pkgs, lib, ... }: + +let + # dependency imports + + inherit (depot.nix) getBins; + inherit (depot.third_party) rustsec-advisory-db; + + bins = getBins pkgs.jq [ + "jq" + ] // getBins pkgs.coreutils [ + "cat" + "printf" + "tee" + "test" + "wc" + ] // getBins pkgs.gnugrep [ + "grep" + ] // getBins pkgs.cargo-audit [ + "cargo-audit" + ] // getBins pkgs.ansi2html [ + "ansi2html" + ] // { + eprintf = depot.tools.eprintf; + }; + + # list of maintainers we may @mention on GitHub + maintainerWhitelist = builtins.attrValues { + inherit (lib.maintainers) + sternenseemann + qyliss + jk + symphorien + erictapen + expipiplus1 + ; + }; + + # buildRustPackage handling + + /* Predicate by which we identify rust packages we are interested in, + i. e. built using `buildRustPackage`. + + Type :: drv -> bool + */ + isRustPackage = v: v ? cargoDeps; + + /* Takes a buildRustPackage derivation and returns a derivation which + builds extracts the `Cargo.lock` of its `cargoDeps` derivation or + `null` if it has none. + + Type: drv -> option<drv> + */ + # TODO(sterni): support cargoVendorDir? + extractCargoLock = drv: + if !(drv ? cargoDeps.outPath) + then null + else pkgs.runCommandNoCC "${drv.name}-Cargo.lock" {} '' + if test -d "${drv.cargoDeps}"; then + cp "${drv.cargoDeps}/Cargo.lock" "$out" + fi + + if test -f "${drv.cargoDeps}"; then + tar -xO \ + --no-wildcards-match-slash --wildcards \ + -f "${drv.cargoDeps}" \ + '*/Cargo.lock' \ + > "$out" + fi + ''; + + # nixpkgs traversal + + # Condition for us to recurse: Either at top-level or recurseForDerivation. + recurseInto = path: x: path == [] || + (lib.isAttrs x && (x.recurseForDerivations or false)); + + # Returns the value or false if an eval error occurs. + tryEvalOrFalse = v: (builtins.tryEval v).value; + + /* Traverses nixpkgs as instructed by `recurseInto` and collects + the attribute and lockfile derivation of every rust package it + encounters into a list. + + Type :: attrs + -> list { + attr :: list<str>; + lock :: option<drv>; + maintainers :: list<maintainer>; + } + */ + allLockFiles = + let + go = path: x: + let + isDrv = tryEvalOrFalse (lib.isDerivation x); + doRec = tryEvalOrFalse (recurseInto path x); + isRust = tryEvalOrFalse (isRustPackage x); + in + if doRec then lib.concatLists ( + lib.mapAttrsToList (n: go (path ++ [ n ])) x + ) else if isDrv && isRust then [ + { + attr = path; + lock = extractCargoLock x; + maintainers = x.meta.maintainers or []; + } + ] else []; + in go []; + + # Report generation and formatting + + reportFor = { attr, lock, maintainers ? [] }: let + # naรฏve attribute path to Nix syntax conversion + strAttr = lib.concatStringsSep "." attr; + strMaintainers = lib.concatMapStringsSep " " (m: "@${m.github}") ( + builtins.filter (x: builtins.elem x maintainerWhitelist) maintainers + ); + in + if lock == null + then pkgs.emptyFile + else depot.nix.runExecline "${strAttr}-vulnerability-report" {} [ + "pipeline" [ + bins.cargo-audit + "audit" "--json" + "-n" "--db" rustsec-advisory-db + "-f" lock + ] + "importas" "out" "out" + "redirfd" "-w" "1" "$out" + bins.jq "-rj" "-f" ./format-audit-result.jq + "--arg" "attr" strAttr + "--arg" "maintainers" strMaintainers + ]; + + # GHMF in issues splits paragraphs on newlines + description = lib.concatMapStringsSep "\n\n" ( + builtins.replaceStrings [ "\n" ] [ " " ] + ) [ + '' + The vulnerability report below was generated by + [nixpkgs-crate-holes](https://code.tvl.fyi/tree/users/sterni/nixpkgs-crate-holes) + which extracts the `Cargo.lock` file of each package in nixpkgs with a + `cargoDeps` attribute and passes it to + [cargo-audit](https://github.com/RustSec/rustsec/tree/main/cargo-audit) + using RustSec's + [advisory-db at ${builtins.substring 0 7 rustsec-advisory-db.rev}](https://github.com/RustSec/advisory-db/tree/${rustsec-advisory-db.rev}/). + '' + '' + Feel free to report any problems or suggest improvements (I have an email + address on my profile and hang out on Matrix/libera.chat as sterni)! + Tick off any reports that have been fixed in the meantime. + '' + '' + Note: A vulnerability in a dependency does not necessarily mean the dependent + package is vulnerable, e. g. when a vulnerable function isn't used. + '' + ]; + + runInstructions = '' + <details> + <summary> + Generating Cargo.lock vulnerability reports + + </summary> + + If you have a checkout of [depot](https://code.tvl.fyi/about/), you can generate this report using: + + ``` + nix-build -A users.sterni.nixpkgs-crate-holes.full \ + --argstr nixpkgsPath /path/to/nixpkgs + ``` + + If you want a more detailed report for a single attribute of nixpkgs, use: + + ``` + nix-build -A users.sterni.nixpkgs-crate-holes.single \ + --argstr nixpkgsPath /path/to/nixpkgs --arg attr '[ "ripgrep" ]' + ``` + + </details> + ''; + + defaultNixpkgsArgs = { allowBroken = false; }; + + reportForNixpkgs = + { nixpkgsPath + , nixpkgsArgs ? defaultNixpkgsArgs + }@args: + + let + reports = builtins.map reportFor ( + allLockFiles (import nixpkgsPath nixpkgsArgs) + ); + in + + depot.nix.runExecline "nixpkgs-rust-pkgs-vulnerability-report.md" { + stdin = lib.concatMapStrings (report: "${report}\n") reports; + } [ + "importas" "out" "out" + "redirfd" "-w" "1" "$out" + # Print introduction paragraph for the issue + "if" [ bins.printf "%s\n\n" description ] + # Print all reports + "foreground" [ + "forstdin" "-E" "report" bins.cat "$report" + ] + # Print stats at the end (mostly as a gimmick), we already know how many + # attributes there are and count the attributes with vulnerability by + # finding the number of checkable list entries in the output. + "backtick" "-E" "vulnerableCount" [ + "pipeline" [ + bins.grep "^- \\[ \\]" "$out" + ] + bins.wc "-l" + ] + "if" [ + bins.printf + "\n%s of %s checked attributes have vulnerable dependencies.\n\n" + "$vulnerableCount" + (toString (builtins.length reports)) + ] + "if" [ + bins.printf "%s\n\n" runInstructions + ] + ]; + + singleReport = + { # Attribute to check: string or list of strings (attr path) + attr + # Path to importable nixpkgs checkout + , nixpkgsPath + # Arguments to pass to nixpkgs + , nixpkgsArgs ? defaultNixpkgsArgs + }: + + let + attr' = if builtins.isString attr then [ attr ] else attr; + drv = lib.getAttrFromPath attr' (import nixpkgsPath nixpkgsArgs); + lockFile = extractCargoLock drv; + strAttr = lib.concatStringsSep "." attr'; + in + + depot.nix.runExecline "${strAttr}-report.html" {} [ + "importas" "out" "out" + "backtick" "-I" "-E" "-N" "report" [ + bins.cargo-audit "audit" + "--quiet" + "-n" "--db" rustsec-advisory-db + "-f" lockFile + ] + "pipeline" [ + "ifte" [ + bins.printf "%s" "$report" + ] [ + bins.printf "%s\n" "No vulnerabilities found" + ] + bins.test "-n" "$report" + ] + "pipeline" [ + bins.tee "/dev/stderr" + ] + "redirfd" "-w" "1" "$out" + bins.ansi2html + ]; + +in { + full = reportForNixpkgs; + single = singleReport; + + inherit + extractCargoLock + allLockFiles + ; + + # simple sanity check, doesn't cover everything, but testing the full report + # is quite expensive in terms of evaluation. + testSingle = singleReport { + nixpkgsPath = depot.third_party.nixpkgs.path; + attr = [ "ripgrep" ]; + }; + + meta.targets = [ "testSingle" ]; +} diff --git a/users/sterni/nixpkgs-crate-holes/format-audit-result.jq b/users/sterni/nixpkgs-crate-holes/format-audit-result.jq new file mode 100644 index 000000000000..e3147b8016c1 --- /dev/null +++ b/users/sterni/nixpkgs-crate-holes/format-audit-result.jq @@ -0,0 +1,61 @@ +# Link to human-readable advisory info for a given vulnerability +def link: + [ "https://rustsec.org/advisories/", .advisory.id, ".html" ] | add; + +# Format a list of version constraints +def version_list: + [ .[] | "`" + . + "`" ] | join("; "); + +# show paths to fixing this vulnerability: +# +# - if there are patched releases, show them (the version we are using presumably +# predates the vulnerability discovery, so we likely want to upgrade to a +# patched release). +# - if there are no patched releases, show the unaffected versions (in case we +# want to downgrade). +# - otherwise we state that no unaffected versions are available at this time. +# +# This logic should be useful, but is slightly dumber than cargo-audit's +# suggestion when using the non-JSON output. +def patched: + if .versions.patched == [] then + if .versions.unaffected != [] then + "unaffected: " + (.versions.unaffected | version_list) + else + "no unaffected version available" + end + else + "patched: " + (.versions.patched | version_list) + end; + +# if the vulnerability has aliases (like CVE-*) emit them in parens +def aliases: + if .advisory.aliases == [] then + "" + else + [ " (", (.advisory.aliases | join(", ")), ")" ] | add + end; + +# each vulnerability is rendered as a (normal) sublist item +def format_vulnerability: + [ " - " + , .package.name, " ", .package.version, ": " + , "[", .advisory.id, "](", link, ")" + , aliases + , ", ", patched + , "\n" + ] | add; + +# be quiet if no found vulnerabilities, otherwise render a GHFM checklist item +if .vulnerabilities.found | not then + "" +else + ([ "- [ ] " + , "`", $attr, "`: " + , (.vulnerabilities.count | tostring) + , " vulnerabilities in Cargo.lock" + , if $maintainers != "" then " (cc " + $maintainers + ")" else "" end + , "\n" + ] + (.vulnerabilities.list | map(format_vulnerability)) + ) | add +end diff --git a/users/tazjin/OWNERS b/users/tazjin/OWNERS new file mode 100644 index 000000000000..c86f6eaa6adb --- /dev/null +++ b/users/tazjin/OWNERS @@ -0,0 +1,3 @@ +inherited: false +owners: + - tazjin diff --git a/users/tazjin/aoc2019/default.nix b/users/tazjin/aoc2019/default.nix new file mode 100644 index 000000000000..ce3146d1f74e --- /dev/null +++ b/users/tazjin/aoc2019/default.nix @@ -0,0 +1,22 @@ +# Solutions for Advent of Code 2019, written in Emacs Lisp. +# +# For each day a new file is created as "solution-day$n.el". +{ depot, ... }: + +let + inherit (builtins) attrNames filter head listToAttrs match readDir; + dir = readDir ./.; + matchSolution = match "solution-(.*)\.el"; + isSolution = f: (matchSolution f) != null; + getDay = f: head (matchSolution f); + + solutionFiles = filter (e: dir."${e}" == "regular" && isSolution e) (attrNames dir); + solutions = map (f: let day = getDay f; in { + name = day; + value = depot.nix.writeElispBin { + name = "aoc2019"; + deps = p: with p; [ dash s ht ]; + src = ./. + ("/" + f); + }; + }) solutionFiles; +in listToAttrs solutions diff --git a/users/tazjin/aoc2019/solution-day1.el b/users/tazjin/aoc2019/solution-day1.el new file mode 100644 index 000000000000..d805c22ec870 --- /dev/null +++ b/users/tazjin/aoc2019/solution-day1.el @@ -0,0 +1,28 @@ +;; Advent of Code 2019 - Day 1 +(require 'dash) + +;; Puzzle 1: + +(defvar day-1/input + '(83285 96868 121640 51455 128067 128390 141809 52325 68310 140707 124520 149678 + 87961 52040 133133 52203 117483 85643 84414 86558 65402 122692 88565 61895 + 126271 128802 140363 109764 53600 114391 98973 124467 99574 69140 144856 + 56809 149944 138738 128823 82776 77557 51994 74322 64716 114506 124074 + 73096 97066 96731 149307 135626 121413 69575 98581 50570 60754 94843 72165 + 146504 53290 63491 50936 79644 119081 70218 85849 133228 114550 131943 + 67288 68499 80512 148872 99264 119723 68295 90348 146534 52661 99146 95993 + 130363 78956 126736 82065 77227 129950 97946 132345 107137 79623 148477 + 88928 118911 75277 97162 80664 149742 88983 74518)) + +(defun calculate-fuel (mass) + (- (/ mass 3) 2)) + +(message "Solution to day1/1: %d" (apply #'+ (-map #'calculate-fuel day-1/input))) + +;; Puzzle 2: +(defun calculate-recursive-fuel (mass) + (let ((fuel (calculate-fuel mass))) + (if (< fuel 0) 0 + (+ fuel (calculate-recursive-fuel fuel))))) + +(message "Solution to day1/2: %d" (apply #'+ (-map #'calculate-recursive-fuel day-1/input))) diff --git a/users/tazjin/aoc2019/solution-day2.el b/users/tazjin/aoc2019/solution-day2.el new file mode 100644 index 000000000000..6ecac1e2016c --- /dev/null +++ b/users/tazjin/aoc2019/solution-day2.el @@ -0,0 +1,53 @@ +;; -*- lexical-binding: t; -*- +;; Advent of Code 2019 - Day 2 +(require 'dash) +(require 'ht) + +(defvar day2/input + [1 0 0 3 1 1 2 3 1 3 4 3 1 5 0 3 2 1 9 19 1 19 5 23 1 13 23 27 1 27 6 31 + 2 31 6 35 2 6 35 39 1 39 5 43 1 13 43 47 1 6 47 51 2 13 51 55 1 10 55 + 59 1 59 5 63 1 10 63 67 1 67 5 71 1 71 10 75 1 9 75 79 2 13 79 83 1 9 + 83 87 2 87 13 91 1 10 91 95 1 95 9 99 1 13 99 103 2 103 13 107 1 107 10 + 111 2 10 111 115 1 115 9 119 2 119 6 123 1 5 123 127 1 5 127 131 1 10 + 131 135 1 135 6 139 1 10 139 143 1 143 6 147 2 147 13 151 1 5 151 155 1 + 155 5 159 1 159 2 163 1 163 9 0 99 2 14 0 0]) + +;; Puzzle 1 + +(defun day2/single-op (f state idx) + (let* ((a (aref state (aref state (+ 1 idx)))) + (b (aref state (aref state (+ 2 idx)))) + (p (aref state (+ 3 idx))) + (result (funcall f a b))) + (aset state p (funcall f a b)))) + +(defun day2/operate (state idx) + (pcase (aref state idx) + (99 (aref state 0)) + (1 (day2/single-op #'+ state idx) + (day2/operate state (+ 4 idx))) + (2 (day2/single-op #'* state idx) + (day2/operate state (+ 4 idx))) + (other (error "Unknown opcode: %s" other)))) + +(defun day2/program-with-inputs (noun verb) + (let* ((input (copy-tree day2/input t))) + (aset input 1 noun) + (aset input 2 verb) + (day2/operate input 0))) + +(message "Solution to day2/1: %s" (day2/program-with-inputs 12 2)) + +;; Puzzle 2 +(let* ((used (ht)) + (noun 0) + (verb 0) + (result (day2/program-with-inputs noun verb))) + (while (/= 19690720 result) + (setq noun (random 100)) + (setq verb (random 100)) + (unless (ht-get used (format "%d%d" noun verb)) + (ht-set used (format "%d%d" noun verb) t) + (setq result (day2/program-with-inputs noun verb)))) + + (message "Solution to day2/2: %s%s" noun verb)) diff --git a/users/tazjin/aoc2019/solution-day3.el b/users/tazjin/aoc2019/solution-day3.el new file mode 100644 index 000000000000..b7dfdd245fb1 --- /dev/null +++ b/users/tazjin/aoc2019/solution-day3.el @@ -0,0 +1,64 @@ +;; -*- lexical-binding: t; -*- +;; Advent of Code 2019 - Day 3 + +(require 'cl-lib) +(require 'dash) +(require 'ht) +(require 's) + +(defvar day3/input/wire1 + "R1010,D422,L354,U494,L686,U894,R212,U777,L216,U9,L374,U77,R947,U385,L170,U916,R492,D553,L992,D890,L531,U360,R128,U653,L362,U522,R817,U198,L126,D629,L569,U300,L241,U145,R889,D196,L450,D576,L319,D147,R985,U889,L941,U837,L608,D77,L864,U911,L270,D869,R771,U132,L249,U603,L36,D328,L597,U992,L733,D370,L947,D595,L308,U536,L145,U318,R55,D773,R175,D505,R483,D13,R780,U778,R445,D107,R490,U245,L587,U502,R446,U639,R150,U35,L455,D522,R866,U858,R394,D975,R513,D378,R58,D646,L374,D675,R209,U228,R530,U543,L480,U677,L912,D164,L573,U587,L784,D626,L994,U250,L215,U985,R684,D79,L877,U811,L766,U617,L665,D246,L408,U800,L360,D272,L436,U138,R240,U735,L681,U68,L608,D59,R532,D808,L104,U968,R887,U819,R346,U698,L317,U582,R516,U55,L303,U607,L457,U479,L510,D366,L583,U519,R878,D195,R970,D267,R842,U784,R9,D946,R833,D238,L232,D94,L860,D47,L346,U951,R491,D745,R849,U273,R263,U392,L341,D808,R696,U326,R886,D296,L865,U833,R241,U644,R729,D216,R661,D712,L466,D699,L738,U5,L556,D693,R912,D13,R48,U63,L877,U628,L689,D929,R74,U924,R612,U153,R417,U425,L879,D378,R79,D248,L3,U519,R366,U281,R439,D823,R149,D668,R326,D342,L213,D735,R504,U265,L718,D842,L565,U105,L214,U963,R518,D681,R642,U170,L111,U6,R697,U572,R18,U331,L618,D255,R534,D322,L399,U595,L246,U651,L836,U757,R417,D795,R291,U759,L568,U965,R828,D570,R350,U317,R338,D173,L74,D833,L650,D844,L70,U913,R594,U407,R674,D684,L481,D564,L128,D277,R851,D274,L435,D582,R469,U729,R387,D818,R443,U504,R414,U8,L842,U845,R275,U986,R53,U660,R661,D225,R614,U159,R477") + +(defvar day3/input/wire2 + "L1010,D698,R442,U660,L719,U702,L456,D86,R938,D177,L835,D639,R166,D285,L694,U468,L569,D104,L234,D574,L669,U299,L124,D275,L179,D519,R617,U72,L985,D248,R257,D276,L759,D834,R490,U864,L406,U181,R911,U873,R261,D864,R260,U759,R648,U158,R308,D386,L835,D27,L745,U91,R840,U707,R275,U543,L663,U736,L617,D699,R924,U103,R225,U455,R708,U319,R569,U38,R315,D432,L179,D975,R519,D546,L295,U680,L685,U603,R262,D250,R7,U171,R261,U519,L832,U534,L471,U431,L474,U886,R10,D179,L79,D555,R452,U452,L832,U863,L367,U538,L237,D160,R441,U605,R942,U259,L811,D552,R646,D353,L225,D94,L35,D307,R752,U23,R698,U610,L379,D932,R698,D751,R178,D347,R325,D156,R471,D555,R558,D593,R773,U2,L955,U764,L735,U438,R364,D640,L757,U534,R919,U409,R361,U407,R336,D808,R877,D648,R610,U198,R340,U94,R795,D667,R811,U975,L965,D224,R565,D681,L64,U567,R621,U922,L665,U329,R242,U592,L727,D481,L339,U402,R213,D280,R656,U169,R976,D962,L294,D505,L251,D689,L497,U133,R230,D441,L90,D220,L896,D657,L500,U331,R502,U723,R762,D613,L447,D256,L226,U309,L935,U384,L740,D459,R309,D707,R952,D747,L304,D105,R977,D539,R941,D21,R291,U216,R132,D543,R515,U453,L854,D42,R982,U102,L469,D639,R559,D68,R302,U734,R980,D214,R107,D191,L730,D793,L63,U17,R807,U196,R412,D592,R330,D941,L87,D291,L44,D94,L272,D780,R968,U837,L712,D704,R163,U981,R537,U778,R220,D303,L196,D951,R163,D446,R11,D623,L72,D778,L158,U660,L189,D510,L247,D716,L89,U887,L115,U114,L36,U81,R927,U293,L265,U183,R331,D267,R745,D298,L561,D918,R299,U810,L322,U679,L739,D854,L581,U34,L862,D779,R23") + +;; Puzzle 1 + +(defun wire-from (raw) + (-map (lambda (s) + (cons (substring s 0 1) (string-to-number (substring s 1)))) + (s-split "," raw))) + +(defun day3/move (x y next) + (cl-flet ((steps (by op) + (-map op (reverse (number-sequence 1 by))))) + (pcase next + (`("L" . ,by) (steps by (lambda (n) (cons (- x n) y)))) + (`("R" . ,by) (steps by (lambda (n) (cons (+ x n) y)))) + (`("U" . ,by) (steps by (lambda (n) (cons x (+ y n))))) + (`("D" . ,by) (steps by (lambda (n) (cons x (- y n)))))))) + +(defun day3/wire-points (wire) + (let ((points (ht)) + (point-list (-reduce-from + (lambda (acc point) + (-let* (((x . y) (car acc)) + (next (day3/move x y point))) + (-concat next acc))) + '((0 . 0)) wire))) + (-map (-lambda ((s . p)) (ht-set! points p s)) + (-zip (reverse (number-sequence 0 (- (length point-list) 1))) point-list)) + (ht-remove! points '(0 . 0)) + points)) + +(defun day3/closest-intersection (crossed-points) + (car (-sort #'< + (-map (-lambda ((x . y)) + (+ (abs x) (abs y))) + crossed-points)))) + +(defun day3/minimum-steps (wire1 wire2 crossed) + (car (-sort #'< + (-map (-lambda (p) + (+ (ht-get wire1 p) (ht-get wire2 p))) + crossed)))) + +;; Example: +(let* ((wire1-points (day3/wire-points (wire-from day3/input/wire1))) + (wire2-points (day3/wire-points (wire-from day3/input/wire2))) + (crossed-points (-filter (lambda (p) (ht-contains? wire1-points p)) + (ht-keys wire2-points)))) + (message "Solution for day3/1: %d" (day3/closest-intersection crossed-points)) + (message "Solution for day3/2: %d" (day3/minimum-steps wire1-points + wire2-points + crossed-points))) diff --git a/users/tazjin/aoc2019/solution-day4.el b/users/tazjin/aoc2019/solution-day4.el new file mode 100644 index 000000000000..2805f3f4e9cd --- /dev/null +++ b/users/tazjin/aoc2019/solution-day4.el @@ -0,0 +1,73 @@ +;; -*- lexical-binding: t; -*- +;; Advent of Code 2019 - Day 4 + +(require 'cl-lib) +(require 'dash) + +;; Puzzle 1 + +(defun day4/to-digits (num) + "Convert NUM to a list of its digits." + (cl-labels ((steps (n digits) + (if (= n 0) digits + (steps (/ n 10) (cons (% n 10) digits))))) + (steps num '()))) + +(defvar day4/input (-map #'day4/to-digits (number-sequence 128392 643281))) + +(defun day4/filter-password (digits) + "Determines whether the given rules match the supplied + number." + + (and + ;; It is a six digit number + (= 6 (length digits)) + + ;; Value is within the range given in puzzle input + ;; (noop because the range is generated from the input) + + ;; Two adjacent digits are the same (like 22 in 122345). + (car (-reduce-from (-lambda ((acc . prev) next) + (cons (or acc (= prev next)) next)) + '(nil . 0) digits)) + + ;; Going from left to right, the digits never decrease; they only + ;; ever increase or stay the same (like 111123 or 135679). + (car (-reduce-from (-lambda ((acc . prev) next) + (cons (and acc (>= next prev)) next)) + '(t . 0) digits)))) + +;; Puzzle 2 +;; +;; Additional criteria: If there's matching digits, they're not in a group. + +(cl-defstruct day4/acc state prev count) + +(defun day4/filter-longer-groups (digits) + (let ((res (-reduce-from + (lambda (acc next) + (cond ;; sequence is broken and count was at 1 -> + ;; match! + ((and (= (day4/acc-count acc) 2) + (/= (day4/acc-prev acc) next)) + (setf (day4/acc-state acc) t)) + + ;; sequence continues, counter increment! + ((= (day4/acc-prev acc) next) + (setf (day4/acc-count acc) (+ 1 (day4/acc-count acc)))) + + ;; sequence broken, reset counter + ((/= (day4/acc-prev acc) next) + (setf (day4/acc-count acc) 1))) + + (setf (day4/acc-prev acc) next) + acc) + (make-day4/acc :prev 0 :count 0) digits))) + (or (day4/acc-state res) + (= 2 (day4/acc-count res))))) + +(let* ((simple (-filter #'day4/filter-password day4/input)) + (complex (-filter #'day4/filter-longer-groups simple))) + (message "Solution to day4/1: %d" (length simple)) + (message "Solution to day4/2: %d" (length complex))) + diff --git a/users/tazjin/aoc2020/default.nix b/users/tazjin/aoc2020/default.nix new file mode 100644 index 000000000000..7a7309ac5aaa --- /dev/null +++ b/users/tazjin/aoc2020/default.nix @@ -0,0 +1,22 @@ +# Solutions for Advent of Code 2020, written in Emacs Lisp. +# +# For each day a new file is created as "solution-day$n.el". +{ depot, pkgs, ... }: + +let + inherit (builtins) attrNames filter head listToAttrs match readDir; + dir = readDir ./.; + matchSolution = match "solution-(.*)\.el"; + isSolution = f: (matchSolution f) != null; + getDay = f: head (matchSolution f); + + solutionFiles = filter (e: dir."${e}" == "regular" && isSolution e) (attrNames dir); + solutions = map (f: let day = getDay f; in depot.nix.writeElispBin { + name = day; + deps = p: with p; [ dash s ht p.f ]; + src = ./. + ("/" + f); + }) solutionFiles; +in pkgs.symlinkJoin { + name = "aoc2020"; + paths = solutions; +} diff --git a/users/tazjin/aoc2020/solution-day1.el b/users/tazjin/aoc2020/solution-day1.el new file mode 100644 index 000000000000..a04f43d15197 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day1.el @@ -0,0 +1,44 @@ +;; Advent of Code 2020 - Day 1 +(require 'cl) +(require 'ht) +(require 'dash) + +(defmacro hash-set (&rest elements) + "Define a hash-table with empty values, for use as a set." + (cons 'ht (-map (lambda (x) (list x nil)) elements))) + +;; Puzzle 1: + +(defvar day1/input + (hash-set 1645 1995 1658 1062 1472 1710 1424 1823 1518 1656 1811 1511 1320 1521 1395 + 1996 1724 1666 1637 1504 1766 534 1738 1791 1372 1225 1690 1949 1495 1436 1166 + 1686 1861 1889 1887 997 1202 1478 833 1497 1459 1717 1272 1047 1751 1549 1204 + 1230 1260 1611 1506 1648 1354 1415 1615 1327 1622 1592 1807 1601 1026 1757 1376 + 1707 1514 1905 1660 1578 1963 1292 390 1898 1019 1580 1499 1830 1801 1881 1764 + 1442 1838 1088 1087 1040 1349 1644 1908 1697 1115 1178 1224 1810 1445 1594 1894 + 1287 1676 1435 1294 1796 1350 1685 1118 1488 1726 1696 1190 1538 1780 1806 1207 + 1346 1705 983 1249 1455 2002 1466 1723 1227 1390 1281 1715 1603 1862 1744 1774 + 1385 1312 1654 1872 1142 1273 1508 1639 1827 1461 1795 1533 1304 1417 1984 28 + 1693 1951 1391 1931 1179 1278 1400 1361 1369 1343 1416 1426 314 1510 1933 1239 + 1218 1918 1797 1255 1399 1229 723 1992 1595 1191 1916 1525 1605 1524 1869 1652 + 1874 1756 1246 1310 1219 1482 1429 1244 1554 1575 1123 1194 1408 1917 1613 1773 + 1809 1987 1733 1844 1423 1718 1714 1923 1503)) + +(message "Solution to day1/1: %s" + (cl-loop for first being the hash-keys of day1/input + for second = (- 2020 first) + when (ht-contains? day1/input second) + return (* first second))) + +;; Puzzle 2: + +(message "Solution to day1/1: %s" + (cl-loop for first being the hash-keys of day1/input + for result = + (cl-loop + for second being the elements of (-drop 1 (ht-keys day1/input)) + for third = (- 2020 first second) + when (ht-contains? day1/input third) + return (* first second third)) + + when result return result)) diff --git a/users/tazjin/aoc2020/solution-day2.el b/users/tazjin/aoc2020/solution-day2.el new file mode 100644 index 000000000000..5993bf3407e4 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day2.el @@ -0,0 +1,54 @@ +;; Advent of Code 2020 - Day 2 + +(require 'cl-lib) +(require 'f) +(require 'ht) +(require 's) +(require 'seq) + +(defvar day2/input + ;; This one was too large to inline. + (s-lines (f-read "/tmp/aoc/day2.txt"))) + +(defun day2/count-letters (password) + (let ((table (ht-create))) + (cl-loop for char across password + for current = (ht-get table char) + do (ht-set table char + (if current (+ 1 current) 1))) + table)) + +(defun day2/parse (input) + (let* ((split (s-split " " input)) + (range (s-split "-" (car split)))) + (list (string-to-number (car range)) + (string-to-number (cadr range)) + (string-to-char (cadr split)) + (caddr split)))) + +(defun day2/count-with-validation (func) + (length (-filter + (lambda (password) + (and (not (seq-empty-p password)) + (apply func (day2/parse password)))) + day2/input))) + +;; Puzzle 1 + +(defun day2/validate-oldjob (min max char password) + (let ((count (ht-get (day2/count-letters password) char))) + (when count + (and (>= count min) + (<= count max))))) + +(message "Solution to day2/1: %s" + (day2/count-with-validation #'day2/validate-oldjob)) + +;; Puzzle 2 + +(defun day2/validate-toboggan (pos1 pos2 char password) + (xor (= char (aref password (- pos1 1))) + (= char (aref password (- pos2 1))))) + +(message "Solution to day2/2: %s" + (day2/count-with-validation #'day2/validate-toboggan)) diff --git a/users/tazjin/aoc2020/solution-day3.el b/users/tazjin/aoc2020/solution-day3.el new file mode 100644 index 000000000000..80ea4a226405 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day3.el @@ -0,0 +1,43 @@ +;; Advent of Code 2020 - Day 3 + +(require 'cl-lib) +(require 'dash) +(require 'f) +(require 's) +(require 'seq) + +(setq day3/input + (-filter (lambda (s) (not (seq-empty-p s))) + (s-lines (f-read "/tmp/aoc/day3.txt")))) + +(setq day3/input-width (length (elt day3/input 0))) +(setq day3/input-height (length day3/input)) + +(defun day3/thing-at-point (x y) + "Pun intentional." + (when (>= day3/input-height y) + (let ((x-repeated (mod (- x 1) day3/input-width))) + (elt (elt day3/input (- y 1)) x-repeated)))) + +(defun day3/slope (x-steps y-steps) + "Produce the objects encountered through this slope until the + bottom of the map." + (cl-loop for x from 1 by x-steps + for y from 1 to day3/input-height by y-steps + collect (day3/thing-at-point x y))) + +;; Puzzle 1 + +(defun day3/count-trees (x-steps y-steps) + (cl-loop for thing being the elements of (day3/slope x-steps y-steps) + count (= thing ?#))) + +(message "Solution to day3/1: One encounters %s trees" (day3/count-trees 3 1)) + +;; Puzzle 2 + +(message "Solution to day3/2 %s" (* (day3/count-trees 1 1) + (day3/count-trees 3 1) + (day3/count-trees 5 1) + (day3/count-trees 7 1) + (day3/count-trees 1 2))) diff --git a/users/tazjin/aoc2020/solution-day4.el b/users/tazjin/aoc2020/solution-day4.el new file mode 100644 index 000000000000..034a40a9558d --- /dev/null +++ b/users/tazjin/aoc2020/solution-day4.el @@ -0,0 +1,98 @@ +;; Advent of Code 2020 - Day 4 + +(require 'cl-lib) +(require 's) +(require 'dash) +(require 'f) + +(cl-defstruct day4/passport + byr ;; Birth Year + iyr ;; Issue Year + eyr ;; Expiration Year + hgt ;; Height + hcl ;; Hair Color + ecl ;; Eye Color + pid ;; Passport ID + cid ;; Country ID + ) + +(defun day4/parse-passport (input) + (let* ((pairs (s-split " " (s-replace "\n" " " input) t)) + (slots + (-map + (lambda (pair) + (pcase-let ((`(,key ,value) (s-split ":" (s-trim pair)))) + (list (intern (format ":%s" key)) value))) + pairs))) + (apply #'make-day4/passport (-flatten slots)))) + +(defun day4/parse-passports (input) + (-map #'day4/parse-passport (s-split "\n\n" input t))) + +(setq day4/input (day4/parse-passports (f-read "/tmp/aoc/day4.txt"))) + +;; Puzzle 1 + +(defun day4/validate (passport) + "Check that all fields except CID are present." + (cl-check-type passport day4/passport) + (and (day4/passport-byr passport) + (day4/passport-iyr passport) + (day4/passport-eyr passport) + (day4/passport-hgt passport) + (day4/passport-hcl passport) + (day4/passport-ecl passport) + (day4/passport-pid passport))) + +(message "Solution to day4/1: %s" (cl-loop for passport being the elements of day4/input + count (day4/validate passport))) + +;; Puzzle 2 + +(defun day4/year-bound (min max value) + (and + (s-matches? (rx (= 4 digit)) value) + (<= min (string-to-number value) max))) + +(defun day4/check-unit (unit min max value) + (and + (string-match (rx (group (+? digit)) (literal unit)) value) + (<= min (string-to-number (match-string 1 value)) max))) + +(defun day4/properly-validate (passport) + "Opting for readable rather than clever here." + (and + (day4/validate passport) + + ;; byr (Birth Year) - four digits; at least 1920 and at most 2002. + (day4/year-bound 1920 2002 (day4/passport-byr passport)) + + ;; iyr (Issue Year) - four digits; at least 2010 and at most 2020. + (day4/year-bound 2010 2020 (day4/passport-iyr passport)) + + ;; eyr (Expiration Year) - four digits; at least 2020 and at most 2030. + (day4/year-bound 2020 2030 (day4/passport-eyr passport)) + + ;; hgt (Height) - a number followed by either cm or in: + ;; If cm, the number must be at least 150 and at most 193. + ;; If in, the number must be at least 59 and at most 76. + (or (day4/check-unit "cm" 150 193 (day4/passport-hgt passport)) + (day4/check-unit "in" 59 76 (day4/passport-hgt passport))) + + ;; hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. + (s-matches? (rx ?# (= 6 hex)) (day4/passport-hcl passport)) + + ;; ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. + (-contains? '("amb" "blu" "brn" "gry" "grn" "hzl" "oth") + (day4/passport-ecl passport)) + + ;; pid (Passport ID) - a nine-digit number, including leading zeroes. + (s-matches? (rx line-start (= 9 digit) line-end) + (day4/passport-pid passport)) + + ;; cid (Country ID) - ignored, missing or not. + )) + +(message "Solution to day4/2: %s" + (cl-loop for passport being the elements of day4/input + count (day4/properly-validate passport))) diff --git a/users/tazjin/aoc2020/solution-day5.el b/users/tazjin/aoc2020/solution-day5.el new file mode 100644 index 000000000000..9bba322902b0 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day5.el @@ -0,0 +1,61 @@ +;; Advent of Code 2020 - Day 5 + +(require 'cl-lib) +(require 'dash) +(require 'f) +(require 'ht) +(require 's) +(require 'seq) + +(defvar day5/input + (-filter (lambda (s) (not (seq-empty-p s))) + (s-lines (f-read "/tmp/aoc/day5.txt")))) + +(defun day5/lower (sequence) + (seq-subseq sequence 0 (/ (length sequence) 2))) + +(defun day5/upper (sequence) + (seq-subseq sequence (/ (length sequence) 2))) + +(defun day5/seat-id (column row) + (+ column (* 8 row))) + +(defun day5/find-seat (boarding-pass) + (let ((rows (number-sequence 0 127)) + (columns (number-sequence 0 7))) + (cl-loop for char across boarding-pass + do (pcase char + (?F (setq rows (day5/lower rows))) + (?B (setq rows (day5/upper rows))) + (?R (setq columns (day5/upper columns))) + (?L (setq columns (day5/lower columns)))) + finally return (day5/seat-id (car columns) (car rows))))) + +;; Puzzle 1 + +(message "Solution to day5/1: %s" + (cl-loop for boarding-pass in day5/input + maximize (day5/find-seat boarding-pass))) + +;; Puzzle 2 + +(defun day5/all-seats-in (row) + (-map (lambda (column) (day5/seat-id column row)) + (number-sequence 0 7))) + +(message "Solution to day5/2: %s" + (let ((all-seats (ht-create))) + (-each (-mapcat #'day5/all-seats-in (number-sequence 1 126)) + (lambda (seat) (ht-set all-seats seat nil))) + + (cl-loop for boarding-pass in day5/input + do (ht-remove all-seats (day5/find-seat boarding-pass)) + + ;; Remove seats that lack adjacent entries, those + ;; are missing on the plane. + finally return + (car + (-filter (lambda (seat) + (and (not (ht-contains? all-seats (- seat 1))) + (not (ht-contains? all-seats (+ seat 1))))) + (ht-keys all-seats)))))) diff --git a/users/tazjin/aoc2020/solution-day6.el b/users/tazjin/aoc2020/solution-day6.el new file mode 100644 index 000000000000..8179c79af2bd --- /dev/null +++ b/users/tazjin/aoc2020/solution-day6.el @@ -0,0 +1,40 @@ +;; Advent of Code 2020 - Day 6 + +(require 'cl-lib) +(require 'dash) +(require 'f) +(require 'ht) +(require 's) + +(defvar day6/input (s-split "\n\n" (f-read "/tmp/aoc/day6.txt") t) + "Input, split into groups (with people in each group still distinct)") + +;; Puzzle 1 + +(defun day6/count-answers (group-answers) + "I suspect doing it this way will be useful in puzzle 2." + (let ((table (ht-create))) + (-each group-answers + (lambda (answer) + (cl-loop for char across answer + do (ht-set table char (+ 1 (or (ht-get table char) + 0)))))) + table)) + +(message "Solution to day6/1: %s" + (cl-loop for group being the elements of day6/input + sum (length + (ht-keys + (day6/count-answers (s-lines group)))))) + +;; Puzzle 2 + +(defun day6/count-unanimous-answers (answers) + (ht-reject (lambda (_key value) (not (= value (length answers)))) + (day6/count-answers answers))) + +(message "Solution to day6/2: %s" + (cl-loop for group being the elements of day6/input + sum (length + (ht-keys + (day6/count-unanimous-answers (s-split "\n" group t)))))) diff --git a/users/tazjin/aoc2020/solution-day7.el b/users/tazjin/aoc2020/solution-day7.el new file mode 100644 index 000000000000..251a85fede02 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day7.el @@ -0,0 +1,92 @@ +;; Advent of Code 2020 - Day 7 + +(require 'cl-lib) +(require 'dash) +(require 'f) +(require 's) +(require 'ht) + +(defvar day7/input + (s-lines (s-chomp (f-read "/tmp/aoc/day7.txt")))) + +(defun day7/parse-bag (input) + (string-match (rx line-start + (group (one-or-more (or letter space))) + "s contain " + (group (one-or-more anything)) + "." line-end) + input) + (cons (match-string 1 input) + (-map + (lambda (content) + (unless (equal content "no other bags") + (progn + (string-match + (rx (group (one-or-more digit)) + space + (group (one-or-more anything) "bag")) + content) + (cons (match-string 2 content) + (string-to-number (match-string 1 content)))))) + (s-split ", " (match-string 2 input))))) + +(defun day7/id-or-next (table bag-type) + (unless (ht-contains? table bag-type) + (ht-set table bag-type (length (ht-keys table)))) + (ht-get table bag-type)) + +(defun day7/build-graph (input &optional flip) + "Represent graph mappings directionally using an adjacency + matrix, because that's probably easiest. + + By default an edge means 'contains', with optional argument + FLIP edges are inverted and mean 'contained by'." + + (let ((bag-mapping (ht-create)) + (graph (let ((length (length input))) + (apply #'vector + (-map (lambda (_) (make-vector length 0)) input))))) + (cl-loop for bag in (-map #'day7/parse-bag input) + for bag-id = (day7/id-or-next bag-mapping (car bag)) + do (-each (-filter #'identity (cdr bag)) + (pcase-lambda (`(,contained-type . ,count)) + (let ((contained-id (day7/id-or-next bag-mapping contained-type))) + (if flip + (aset (aref graph contained-id) bag-id count) + (aset (aref graph bag-id) contained-id count)))))) + (cons bag-mapping graph))) + +;; Puzzle 1 + +(defun day7/find-ancestors (visited graph start) + (ht-set visited start t) + (cl-loop for bag-count being the elements of (aref graph start) + using (index bag-id) + when (and (> bag-count 0) + (not (ht-contains? visited bag-id))) + do (day7/find-ancestors visited graph bag-id))) + +(message + "Solution to day7/1: %s" + (pcase-let* ((`(,mapping . ,graph) (day7/build-graph day7/input t)) + (shiny-gold-id (ht-get mapping "shiny gold bag")) + (visited (ht-create))) + (day7/find-ancestors visited graph shiny-gold-id) + (- (length (ht-keys visited)) 1))) + +;; Puzzle 2 + +(defun ht-find-by-value (table value) + (ht-find (lambda (_key item-value) (equal item-value value)) table)) + +(defun day7/count-contained-bags (mapping graph start) + (cl-loop for bag-count being the elements of (aref graph start) + using (index bag-id) + when (> bag-count 0) + sum (+ bag-count + (* bag-count (day7/count-contained-bags mapping graph bag-id))))) + +(message "Solution to day7/2: %s" + (pcase-let* ((`(,mapping . ,graph) (day7/build-graph day7/input)) + (shiny-gold-id (ht-get mapping "shiny gold bag"))) + (day7/count-contained-bags mapping graph shiny-gold-id))) diff --git a/users/tazjin/aoc2020/solution-day8.el b/users/tazjin/aoc2020/solution-day8.el new file mode 100644 index 000000000000..591a07fbf3a0 --- /dev/null +++ b/users/tazjin/aoc2020/solution-day8.el @@ -0,0 +1,63 @@ +;; Advent of Code 2020 - Day + +(require 'cl-lib) +(require 'dash) +(require 'f) +(require 's) + +(setq day8/input + (apply #'vector + (-map (lambda (s) + (pcase-let ((`(,op ,val) (s-split " " s t))) + (cons (intern op) (string-to-number val)))) + (s-lines (s-chomp (f-read "/tmp/aoc/day8.txt")))))) + +(defun day8/step (code position acc) + (if (>= position (length code)) + (cons 'final acc) + + (let ((current (aref code position))) + (aset code position :done) + (pcase current + (:done (cons 'loop acc)) + (`(nop . ,val) (cons (+ position 1) acc)) + (`(acc . ,val) (cons (+ position 1) (+ acc val))) + (`(jmp . ,val) (cons (+ position val) acc)))))) + +;; Puzzle 1 + +(message "Solution to day8/1: %s" + (let ((code (copy-sequence day8/input)) + (position 0) + (acc 0)) + (cl-loop for next = (day8/step code position acc) + when (equal 'loop (car next)) return (cdr next) + do (setq position (car next)) + do (setq acc (cdr next))))) + +;; Puzzle 2 + +(defun day8/flip-at (code pos) + (pcase (aref code pos) + (`(nop . ,val) (aset code pos `(jmp . ,val))) + (`(jmp . ,val) (aset code pos `(nop . ,val))) + (other (error "Unexpected flip op: %s" other)))) + +(defun day8/try-flip (flip-at code position acc) + (day8/flip-at code flip-at) + (cl-loop for next = (day8/step code position acc) + when (equal 'loop (car next)) return nil + when (equal 'final (car next)) return (cdr next) + do (setq position (car next)) + do (setq acc (cdr next)))) + +(message "Solution to day8/2: %s" + (let ((flip-options (cl-loop for op being the elements of day8/input + using (index idx) + for opcode = (car op) + when (or (equal 'nop opcode) + (equal 'jmp opcode)) + collect idx))) + (cl-loop for flip-at in flip-options + for result = (day8/try-flip flip-at (copy-sequence day8/input) 0 0) + when result return result))) diff --git a/users/tazjin/avatar.jpeg b/users/tazjin/avatar.jpeg new file mode 100644 index 000000000000..f6888e01c7dc --- /dev/null +++ b/users/tazjin/avatar.jpeg Binary files differdiff --git a/users/tazjin/blog/.skip-subtree b/users/tazjin/blog/.skip-subtree new file mode 100644 index 000000000000..e7fa50d49bdd --- /dev/null +++ b/users/tazjin/blog/.skip-subtree @@ -0,0 +1 @@ +Subdirectories contain blog posts and static assets only diff --git a/users/tazjin/blog/default.nix b/users/tazjin/blog/default.nix new file mode 100644 index 000000000000..6ac89e46656a --- /dev/null +++ b/users/tazjin/blog/default.nix @@ -0,0 +1,43 @@ +{ depot, lib, pkgs, ... }: + +with depot.nix.yants; + +let + inherit (builtins) hasAttr filter; + + config = { + name = "tazjin's blog"; + baseUrl = "https://tazj.in/blog"; + + footer = '' + <p class="footer"> + <a class="uncoloured-link" href="https://tazj.in">homepage</a> + | + <a class="uncoloured-link" href="https://cs.tvl.fyi/">code</a> + </p> + <p class="lod">เฒ _เฒ </p> + ''; + }; + + inherit (depot.web.blog) post includePost renderPost; + + posts = filter includePost (list post (import ./posts.nix)); + + rendered = pkgs.runCommandNoCC "tazjins-blog" {} '' + mkdir -p $out + + ${lib.concatStringsSep "\n" (map (post: + "cp ${renderPost config post} $out/${post.key}.html" + ) posts)} + ''; + +in { + inherit posts rendered config; + + # Generate embeddable nginx configuration for redirects from old post URLs + oldRedirects = lib.concatStringsSep "\n" (map (post: '' + location ~* ^(/en)?/${post.oldKey} { + return 301 https://tazj.in/blog/${post.key}; + } + '') (filter (hasAttr "oldKey") posts)); +} diff --git a/users/tazjin/blog/posts.nix b/users/tazjin/blog/posts.nix new file mode 100644 index 000000000000..b43598d01358 --- /dev/null +++ b/users/tazjin/blog/posts.nix @@ -0,0 +1,57 @@ +# This file defines all the blog posts. +[ + { + key = "emacs-is-underrated"; + title = "Emacs is the most underrated tool"; + date = 1581286656; + content = ./posts/emacs-is-underrated.md; + draft = true; + } + { + key = "best-tools"; + title = "tazjin's best tools"; + date = 1576800001; + content = ./posts/best-tools.md; + } + { + key = "nixery-layers"; + title = "Nixery: Improved Layering Design"; + date = 1565391600; + content = ./posts/nixery-layers.md; + } + { + key = "reversing-watchguard-vpn"; + title = "Reverse-engineering WatchGuard Mobile VPN"; + date = 1486830338; + content = ./posts/reversing-watchguard-vpn.md; + oldKey = "1486830338"; + } + { + key = "make-object-t-again"; + title = "Make Object <T> Again!"; + date = 1476807384; + content = ./posts/make-object-t-again.md; + oldKey = "1476807384"; + } + { + key = "the-smu-problem"; + title = "The SMU-problem of messaging apps"; + date = 1450354078; + content =./posts/the-smu-problem.md; + oldKey = "1450354078"; + } + { + key = "sick-in-sweden"; + title = "Being sick in Sweden"; + date = 1423995834; + content = ./posts/sick-in-sweden.md; + oldKey = "1423995834"; + } + { + key = "nsa-zettabytes"; + title = "The NSA's 5 zettabytes of data"; + date = 1375310627; + content = ./posts/nsa-zettabytes.md; + oldKey = "1375310627"; + } +] diff --git a/users/tazjin/blog/posts/best-tools.md b/users/tazjin/blog/posts/best-tools.md new file mode 100644 index 000000000000..e4bad8f4cd07 --- /dev/null +++ b/users/tazjin/blog/posts/best-tools.md @@ -0,0 +1,160 @@ +In the spirit of various other "Which X do you use?"-pages I thought it would be +fun to have a little post here that describes which tools I've found to work +well for myself. + +When I say "tools" here, it's not about software - it's about real, physical +tools! + +If something goes on this list that's because I think it's seriously a +best-in-class type of product. + +<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> +- [Media & Tech](#media--tech) + - [Keyboard](#keyboard) + - [Speakers](#speakers) + - [Headphones](#headphones) + - [Earphones](#earphones) + - [Phone](#phone) +- [Other stuff](#other-stuff) + - [Toothbrush](#toothbrush) + - [Shavers](#shavers) + - [Shoulder bag](#shoulder-bag) + - [Wallet](#wallet) +<!-- markdown-toc end --> + +--------- + +# Media & Tech + +## Keyboard + +The best keyboard that money will buy you at the moment is the [Kinesis +Advantage][advantage]. There's a variety of contoured & similarly shaped +keyboards on the market, but the Kinesis is the only one I've tried that has +properly implemented the keywell concept. + +I struggle with RSI issues and the Kinesis actually makes it possible for me to +type for longer periods of time, which always leads to extra discomfort on +laptop keyboards and such. + +Honestly, the Kinesis is probably the best piece of equipment on this entire +list. I own several of them and there will probably be more in the future. They +last forever and your wrists will thank you in the future, even if you do not +suffer from RSI yet. + +[advantage]: https://kinesis-ergo.com/shop/advantage2/ + +## Speakers + +The speakers that I've hooked up to my audio setup (including both record player +& Chromecast / TV) are the [Teufel Motiv 2][motiv-2]. I've had these for over a +decade and they're incredibly good, but unfortunately Teufel no longer makes +them. + +It's possible to grab a pair on eBay occasionally, so keep an eye out if you're +interested! + +[motiv-2]: https://www.teufelaudio.com/uk/pc/motiv-2-p167.html + +## Headphones + +I use the [Bose QC35][qc35] (note: link goes to a newer generation than the one +I own) for their outstanding noise cancelling functionality and decent sound. + +When I first bought them I didn't expect them to end up on this list as the +firmware had issues that made them only barely usable, but Bose has managed to +iron these problems out over time. + +I avoid using Bluetooth when outside and fortunately the QC35 come with an +optional cable that you can plug into any good old 3.5mm jack. + +[qc35]: https://www.bose.co.uk/en_gb/products/headphones/over_ear_headphones/quietcomfort-35-wireless-ii.html + +### Earphones + +Actually, to follow up on the above - most of the time I'm not using (over-ear) +headphones, but (in-ear) earphones - specifically the (**wired!!!**) [Apple +EarPods][earpods]. + +Apple will probably stop selling these soon because they've gotten into the +habit of cancelling all of their good products, so I have a stash of these +around. You will usually find no fewer than 3-4 of them lying around in my +flat. + +[earpods]: https://www.apple.com/uk/shop/product/MNHF2ZM/A/earpods-with-35mm-headphone-plug + +## Phone + +The best phone I have used in recent years is the [iPhone SE][se]. It was the +*last* phone that had a reasonable size (up to 4") *and* a 3.5mm headphone jack. + +Unfortunately, it runs iOS. Despite owning a whole bunch of SEs, I have finally +moved on to an Android phone that is only moderately larger (still by an +annoying amount), but does at least have a headphone jack: The [Samsung Galaxy +S10e][s10e]. + +It has pretty good hardware and I can almost reach 70% of the screen, which is +better than other phones out there right now. Unfortunately it runs Samsung's +impossible-to-remove bloatware on top of Android, but that is still less +annoying to use than iOS. + +QUESTION: This is the only item on this list for which I am actively seeking a +replacement, so if you have any tips about new phones that might fit these +criteria that I've missed please let me know! + +[se]: https://en.wikipedia.org/wiki/IPhone_SE +[s10e]: https://www.phonearena.com/phones/Samsung-Galaxy-S10e_id11114 + +# Other stuff + +## Toothbrush + +The [Philips Sonicare][sonicare] (note: link goes to a newer generation than +mine) is excellent and well worth its money. + +I've had it for a few years and whereas I occasionally had minor teeth issues +before, they seem to be mostly gone now. According to my dentist the state of my +teeth is now usually pretty good and I draw a direct correlation back to this +thing. + +The newer generations come with flashy features like apps and probably more +LEDs, but I suspect that those can just be ignored. + +[sonicare]: https://www.philips.co.uk/c-m-pe/electric-toothbrushes + +## Shavers + +The [Philipps SensoTouch 3D][sensotouch] is excellent. Super-comfortable close +face shave in no time and leaves absolutely no mess around, as far as I can +tell! I've had this for ~5 years and it's not showing any signs of aging yet. + +Another bonus is that its battery time is effectively infinite. I've never had +to worry when bringing it on a longer trip! + +[sensotouch]: https://www.philips.co.uk/c-p/1250X_40/norelco-sensotouch-3d-wet-and-dry-electric-razor-with-precision-trimmer + +## Shoulder bag + +When I moved to London I wanted to stop using backpacks most of the time, as +those are just annoying to deal with when commuting on the tube. + +To work around this I wanted a good shoulder bag with a vertical format (to save +space), but it turned out that there's very few of those around that reach any +kind of quality standard. + +The one I settled on is the [Waterfield Muzetto][muzetto] leather bag. It's one +of those things that comes with a bit of a price tag attached, but it's well +worth it! + +[muzetto]: https://www.sfbags.com/collections/shoulder-messenger-bags/products/muzetto-leather-bag + +## Wallet + +My wallet is the [Bellroy Slim Sleeve][slim-sleeve]. I don't carry cash unless +I'm attending an event in Germany and this wallet fits that lifestyle perfectly. + +It's near indestructible, looks great, is very slim and fits a ton of cards, +business cards, receipts and whatever else you want to be lugging around with +you! + +[slim-sleeve]: https://bellroy.com/products/slim-sleeve-wallet/default/charcoal diff --git a/users/tazjin/blog/posts/emacs-is-underrated.md b/users/tazjin/blog/posts/emacs-is-underrated.md new file mode 100644 index 000000000000..afb8dc889e53 --- /dev/null +++ b/users/tazjin/blog/posts/emacs-is-underrated.md @@ -0,0 +1,233 @@ +TIP: Hello, and thanks for offering to review my draft! This post +intends to convey to people what the point of Emacs is. Not to convert +them to use it, but at least with opening their minds to the +possibility that it might contain valuable things. I don't know if I'm +on track in the right direction, and your input will help me figure it +out. Thanks! + +TODO(tazjin): Restructure sections: Intro -> Introspectability (and +story) -> text-based UIs (which lead to fluidity, muscle memory across +programs and "translatability" of workflows) -> Outro. It needs more +flow! + +TODO(tazjin): Highlight more that it's not about editing: People can +derive useful things from Emacs by just using magit/org/notmuch/etc.! + +TODO(tazjin): Note that there's value in trying Emacs even if people +don't end up using it, similar to how learning languages like Lisp or +Haskell helps grow as a programmer even without using them day-to-day. + +*Real post starts below!* + +--------- + +There are two kinds of people: Those who use Emacs, and those who +think it is a text editor. This post is aimed at those in the second +category. + +Emacs is the most critical piece of software I run. My [Emacs +configuration][emacs-config] has steadily evolved for almost a decade. +Emacs is my window manager, mail client, terminal, git client, +information management system and - perhaps unsurprisingly - text +editor. + +Before going into why I chose to invest so much into this program, +follow me along on a little thought experiment: + +---------- + +Lets say you use a proprietary spreadsheet program. You find that +there are features in it that *almost, but not quite* do what you +want. + +What can you do? You can file a feature request to the company that +makes it and hope they listen, but for the likes of Apple and +Microsoft chances are they won't and there is nothing you can do. + +Let's say you are also running an open-source program for image +manipulation. You again find that some of its features are subtly +different from what you would want them to do. + +Things look a bit different this time - after all, the program is +open-source! You can go and fetch its source code, figure out its +internal structure and wrangle various layers of code into submission +until you find the piece that implements the functionality you want to +change. If you know the language it is written in; you can modify the +feature. + +Now all that's left is figuring out its build system[^1], building and +installing it and moving over to the new version. + +Realistically you are not going to do this much in the real world. The +friction to contributing to projects, especially complex ones, is +often quite high. For minor inconveniences, you might often find +yourself just shrugging and working around them. + +What if it didn't have to be this way? + +------------- + +One of the core properties of Emacs is that it is *introspective* and +*self-documenting*. + +For example: A few years ago, I had just switched over to using +[EXWM][], the Emacs X Window Manager. To launch applications I was +using an Emacs program called Helm that let me select installed +programs interactively and press <kbd>RET</kbd> to execute them. + +This was very useful - until I discovered that if I tried to open a +second terminal window, it would display an error: + + Error: urxvt is already running + +Had this been dmenu, I might have had to go through the whole process +described above to fix the issue. But it wasn't dmenu - it was an +Emacs program, and I did the following things: + +1. I pressed <kbd>C-h k</kbd>[^2] (which means "please tell me what + the following key does"), followed by <kbd>s-d</kbd> (which was my + keybinding for launching programs). + +2. Emacs displayed a new buffer saying, roughly: + + ``` + s-d runs the command helm-run-external-command (found in global-map), + which is an interactive autoloaded compiled Lisp function in + โ.../helm-external.elโ. + + It is bound to s-d. + ``` + + I clicked on the filename. + +3. Emacs opened the file and jumped to the definition of + `helm-run-external-command`. After a few seconds of reading through + the code, I found this snippet: + + ```lisp + (if (get-process proc) + (if helm-raise-command + (shell-command (format helm-raise-command real-com)) + (error "Error: %s is already running" real-com)) + ;; ... the actual code to launch programs followed below ... + ) + ``` + +4. I deleted the outer if-expression which implemented the behaviour I + didn't want, pressed <kbd>C-M-x</kbd> to reload the code and saved + the file. + +The whole process took maybe a minute, and the problem was now gone. + +Emacs isn't just "open-source", it actively encourages the user to +modify it, discover what to modify and experiment while it is running. + +In some sense it is like the experience of the old Lisp machines, a +paradigm that we have completely forgotten. + +--------------- + +Circling back to my opening statement: If Emacs is not a text editor, +then what *is* it? + +The Emacs website says this: + +> [Emacs] is an interpreter for Emacs Lisp, a dialect of the Lisp +> programming language with extensions to support text editing + +The core of Emacs implements the language and the functionality needed +to evaluate and run it, as well as various primitives for user +interface construction such as buffers, windows and frames. + +Every other feature of Emacs is implemented *in Emacs Lisp*. + +The Emacs distribution ships with rudimentary text editing +functionality (and some language-specific support for the most popular +languages), but it also brings with it two IRC clients, a Tetris +implementation, a text-mode web browser, [org-mode][] and many other +tools. + +Outside of the core distribution there is a myriad of available +programs for Emacs: [magit][] (the famous git porcelain), text-based +[HTTP clients][], even interactive [Kubernetes frontends][k8s]. + +What all of these tools have in common is that they use text-based +user interfaces (UI elements like images are used only sparingly in +Emacs), and that they can be introspected and composed like everything +else in Emacs. + +If magit does not expose a git flag I need, it's trivial to add. If I +want a keybinding to jump from a buffer showing me a Kubernetes pod to +a magit buffer for the source code of the container, it only takes a +few lines of Emacs Lisp to implement. + +As proficiency with Emacs Lisp ramps up, the environment becomes +malleable like clay and evolves along with the user's taste and needs. +Muscle memory learned for one program translates seamlessly to others, +and the overall effect is an improvement in *workflow fluidity* that +is difficult to overstate. + +Also, workflows based on Emacs are *stable*. Moving my window +management to Emacs has meant that I'm not subject to the whim of some +third-party developer changing my window layouting features (as they +often do on MacOS). + +To illustrate this: Emacs has development history back to the 1970s, +continuous git history that survived multiple VCS migrations [since +1985][first-commit] (that's 22 years before git itself was released!) +and there is code[^3] implementing interactive functionality that has +survived unmodified in Emacs *since then*. + +--------------- + +Now, what is the point of this post? + +I decided to write this after a recent [tweet][] by @IanColdwater (in +the context of todo-management apps): + +> The fact that it's 2020 and the most viable answer to this appears +> to be Emacs might be the saddest thing I've ever heard + +What bothers me is that people see this as *sad*. Emacs being around +for this long and still being unparalleled for many of the UX +paradigms implemented by its programs is, in my book, incredible - and +not sad. + +How many other paradigms have survived this long? How many other tools +still have fervent followers, amazing [developer tooling][] and a +[vibrant ecosystem][] at this age? + +Steve Yegge [said it best][babel][^5]: Emacs has the Quality Without a +Name. + +What I wish you, the reader, should take away from this post is the +following: + +TODO(tazjin): Figure out what people should take away from this post. +I need to sleep on it. It's something about not dismissing tools just +because of their age, urging them to explore paradigms that might seem +unfamiliar and so on. Ideas welcome. + +--------------- + +[^1]: Wouldn't it be a joy if every project just used Nix? I digress ... +[^2]: These are keyboard shortcuts written in [Emacs Key Notation][ekn]. +[^3]: For example, [functionality for online memes][studly] that + wouldn't be invented for decades to come! +[^4]: ... and some things wrong, but that is an issue for a separate post! +[^5]: And I really *do* urge you to read that post's section on Emacs. + +[emacs-config]: https://git.tazj.in/tree/tools/emacs +[EXWM]: https://github.com/ch11ng/exwm +[helm]: https://github.com/emacs-helm/helm +[ekn]: https://www.gnu.org/software/emacs/manual/html_node/efaq/Basic-keys.html +[org-mode]: https://orgmode.org/ +[magit]: https://magit.vc +[HTTP clients]: https://github.com/pashky/restclient.el +[k8s]: https://github.com/jypma/kubectl +[first-commit]: http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=ce5584125c44a1a2fbb46e810459c50b227a95e2 +[studly]: http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=47bdd84a0a9d20aab934482a64b84d0db63e7532 +[tweet]: https://twitter.com/IanColdwater/status/1220824466525229056 +[developer tooling]: https://github.com/alphapapa/emacs-package-dev-handbook +[vibrant ecosystem]: https://github.com/emacs-tw/awesome-emacs +[babel]: https://sites.google.com/site/steveyegge2/tour-de-babel#TOC-Lisp diff --git a/users/tazjin/blog/posts/make-object-t-again.md b/users/tazjin/blog/posts/make-object-t-again.md new file mode 100644 index 000000000000..420b57c0fde9 --- /dev/null +++ b/users/tazjin/blog/posts/make-object-t-again.md @@ -0,0 +1,98 @@ +A few minutes ago I found myself debugging a strange Java issue related +to Jackson, one of the most common Java JSON serialization libraries. + +The gist of the issue was that a short wrapper using some types from +[Javaslang](http://www.javaslang.io/) was causing unexpected problems: + +```java +public <T> Try<T> readValue(String json, TypeReference type) { + return Try.of(() -> objectMapper.readValue(json, type)); +} +``` + +The signature of this function was based on the original Jackson +`readValue` type signature: + +```java +public <T> T readValue(String content, TypeReference valueTypeRef) +``` + +While happily using my wrapper function I suddenly got an unexpected +error telling me that `Object` is incompatible with the type I was +asking Jackson to de-serialize, which got me to re-evaluate the above +type signature again. + +Lets look for a second at some code that will *happily compile* if you +are using Jackson\'s own `readValue`: + +```java +// This shouldn't compile! +Long l = objectMapper.readValue("\"foo\"", new TypeReference<String>(){}); +``` + +As you can see there we ask Jackson to decode the JSON into a `String` +as enclosed in the `TypeReference`, but assign the result to a `Long`. +And it compiles. And it failes at runtime with +`java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long`. +Huh? + +Looking at the Jackson `readValue` implementation it becomes clear +what\'s going on here: + +```java +@SuppressWarnings({ "unchecked", "rawtypes" }) +public <T> T readValue(String content, TypeReference valueTypeRef) + throws IOException, JsonParseException, JsonMappingException +{ + return (T) _readMapAndClose(/* whatever */); +} +``` + +The function is parameterised over the type `T`, however the only place +where `T` occurs in the signature is in the parameter declaration and +the function return type. Java will happily let you use generic +functions and types without specifying type parameters: + +```java +// Compiles fine! +final List myList = List.of(1,2,3); + +// Type is now myList : List<Object> +``` + +Meaning that those parameters default to `Object`. Now in the code above +Jackson also explicitly casts the return value of its inner function +call to `T`. + +What ends up happening is that Java infers the expected return type from +the context of the `readValue` and then happily uses the unchecked cast +to fit that return type. If the type hints of the context aren\'t strong +enough we simply get `Object` back. + +So what\'s the fix for this? It\'s quite simple: + +```java +public <T> T readValue(String content, TypeReference<T> valueTypeRef) +``` + +By also making the parameter appear in the `TypeReference` we \"bind\" +`T` to the type enclosed in the type reference. The cast can then also +safely be removed. + +The cherries on top of this are: + +1. `@SuppressWarnings({ "rawtypes" })` explicitly disables a + warning that would\'ve caught this + +2. the `readValue` implementation using the less powerful `Class` + class to carry the type parameter does this correctly: `public <T> + T readValue(String content, Class<T> valueType)` + +The big question I have about this is *why* does Jackson do it this way? +Obviously the warning did not just appear there by chance, so somebody +must have thought about this? + +If anyone knows what the reason is, I\'d be happy to hear from you. + +PS: Shoutout to David & Lucia for helping me not lose my sanity over +this. diff --git a/users/tazjin/blog/posts/nixery-layers.md b/users/tazjin/blog/posts/nixery-layers.md new file mode 100644 index 000000000000..3f25ceadce7b --- /dev/null +++ b/users/tazjin/blog/posts/nixery-layers.md @@ -0,0 +1,272 @@ +TIP: This blog post was originally published as a design document for +[Nixery][] and is not written in the same style +as other blog posts. + +Thanks to my colleagues at Google and various people from the Nix community for +reviewing this. + +------ + +# Nixery: Improved Layering + +**Authors**: tazjin@ + +**Reviewers**: so...@, en...@, pe...@ + +**Status**: Implemented + +**Last Updated**: 2019-08-10 + +## Introduction + +This document describes a design for an improved image layering method for use +in Nixery. The algorithm [currently used][grhmc] is designed for a slightly +different use-case and we can improve upon it by making use of more of the +available data. + +## Background / Motivation + +Nixery is a service that uses the [Nix package manager][nix] to build container +images (for runtimes such as Docker), that are served on-demand via the +container [registry protocols][]. A demo instance is available at +[nixery.dev][]. + +In practice this means users can simply issue a command such as `docker pull +nixery.dev/shell/git` and receive an image that was built ad-hoc containing a +shell environment and git. + +One of the major advantages of building container images via Nix (as described +for `buildLayeredImage` in [this blog post][grhmc]) is that the +content-addressable nature of container image layers can be used to provide more +efficient caching characteristics (caching based on layer content) than what is +common with Dockerfiles and other image creation methods (caching based on layer +creation method). + +However, this is constrained by the maximum number of layers supported in an +image (125). A naive approach such as putting each included package (any +library, binary, etc.) in its own layer quickly runs into this limitation due to +the large number of dependencies more complex systems tend to have. In addition, +users wanting to extend images created by Nixery (e.g. via `FROM nixery.dev/โฆ`) +share this layer maximum with the created image - limiting extensibility if all +layers are used up by Nixery. + +In theory the layering strategy of `buildLayeredImage` should already provide +good caching characteristics, but in practice we are seeing many images with +significantly more packages than the number of layers configured, leading to +more frequent cache-misses than desired. + +The current implementation of `buildLayeredImage` inspects a graph of image +dependencies and determines the total number of references (direct & indirect) +to any node in the graph. It then sorts all dependencies by this popularity +metric and puts the first `n - 2` (for `n` being the maximum number of layers) +packages in their own layers, all remaining packages in one layer and the image +configuration in the final layer. + +## Design / Proposal + +## (Close-to) ideal layer-layout using more data + +We start out by considering what a close to ideal layout of layers would look +like for a simple use-case. + +![Ideal layout](/static/img/nixery/ideal_layout.webp) + +In this example, counting the total number of references to each node in the +graph yields the following result: + +| pkg | refs | +|-------|------| +| E | 3 | +| D | 2 | +| F | 2 | +| A,B,C | 1 | + +Assuming we are constrained to 4 layers, the current algorithm would yield these layers: + +``` +L1: E +L2: D +L3: F +L4: A, B, C +``` + +The initial proposal for this design is that additional data should be +considered in addition to the total number of references, in particular a +distinction should be made between direct and indirect references. Packages that +are only referenced indirectly should be merged with their parents. + +This yields the following table: + +| pkg | direct | indirect | +|-------|--------|----------| +| E | 3 | 3 | +| D | 2 | 2 | +| F | *1* | 2 | +| A,B,C | 1 | 1 | + +Despite having two indirect references, F is in fact only being referred to +once. Assuming that we have no other data available outside of this graph, we +have no reason to assume that F has any popularity outside of the scope of D. +This might yield the following layers: + +``` +L1: E +L2: D, F +L3: A +L4: B, C +``` + +D and F were grouped, while the top-level references (i.e. the packages +explicitly requested by the user) were split up. + +An assumption is introduced here to justify this split: The top-level packages +is what the user is modifying directly, and those groupings are likely +unpredictable. Thus it is opportune to not group top-level packages in the same +layer. + +This raises a new question: Can we make better decisions about where to split +the top-level? + +## (Even closer to) ideal layering using (even) more data + +So far when deciding layer layouts, only information immediately available in +the build graph of the image has been considered. We do however have much more +information available, as we have both the entire nixpkgs-tree and potentially +other information (such as download statistics). + +We can calculate the total number of references to any derivation in nixpkgs and +use that to rank the popularity of each package. Packages within some percentile +can then be singled out as good candidates for a separate layer. + +When faced with a splitting decision such as in the last section, this data can +aid the decision. Assume for example that package B in the above is actually +`openssl`, which is a very popular package. Taking this into account would +instead yield the following layers: + +``` +L1: E, +L2: D, F +L3: B, +L4: A, C +``` + +## Layer budgets and download size considerations + +As described in the introduction, there is a finite amount of layers available +for each image (the โlayer budgetโ). When calculating the layer distribution, we +might end up with the โidealโ list of layers that we would like to create. Using +our previous example: + +``` +L1: E, +L2: D, F +L3: A +L4: B +L5: C +``` + +If we only have a layer budget of 4 available, something needs to be merged into +the same layer. To make a decision here we could consider only the package +popularity, but there is in fact another piece of information that has not come +up yet: The actual size of the package. + +Presumably a user would not mind downloading a library that is a few kilobytes +in size repeatedly, but they would if it was a 200 megabyte binary instead. + +Conversely if a large binary was successfully cached, but an extremely popular +small library is not, the total download size might also grow to irritating +levels. + +To avoid this we can calculate a merge rating: + + merge_rating(pkg) = popularity_percentile(pkg) ร size(pkg.subtree) + +Packages with a low merge rating would be merged together before packages with +higher merge ratings. + +## Implementation + +There are two primary components of the implementation: + +1. The layering component which, given an image specification, decides the image + layers. + +2. The popularity component which, given the entire nixpkgs-tree, calculates the + popularity of packages. + +## Layering component + +It turns out that graph theoryโs concept of [dominator trees][] maps reasonably +well onto the proposed idea of separating direct and indirect dependencies. This +becomes visible when creating the dominator tree of a simple example: + +![Example without extra edges](/static/img/nixery/example_plain.webp) + +Before calculating the dominator tree, we inspect each node and insert extra +edges from the root for packages that match a certain popularity or size +threshold. In this example, G is popular and an extra edge is inserted: + +![Example with extra edges](/static/img/nixery/example_extra.webp) + +Calculating the dominator tree of this graph now yields our ideal layer +distribution: + +![Dominator tree of example](/static/img/nixery/dominator.webp) + +The nodes immediately dominated by the root node can now be โharvestedโ as image +layers, and merging can be performed as described above until the result fits +into the layer budget. + +To implement this, the layering component uses the [gonum/graph][] library which +supports calculating dominator trees. The program is fed with Nixโs +`exportReferencesGraph` (which contains the runtime dependency graph and runtime +closure size) as well as the popularity data and layer budget. It returns a list +of layers, each specifying the paths it should contain. + +Nix invokes this program and uses the output to create a derivation for each +layer, which is then built and returned to Nixery as usual. + +TIP: This is implemented in [`layers.go`][layers.go] in Nixery. The file starts +with an explanatory comment that talks through the process in detail. + +## Popularity component + +The primary issue in calculating the popularity of each package in the tree is +that we are interested in the runtime dependencies of a derivation, not its +build dependencies. + +To access information about the runtime dependency, the derivation actually +needs to be built by Nix - it can not be inferred because Nix does not know +which store paths will still be referenced by the build output. + +However for packages that are cached in the NixOS cache, we can simply inspect +the `narinfo`-files and use those to determine popularity. + +Not every package in nixpkgs is cached, but we can expect all *popular* packages +to be cached. Relying on the cache should therefore be reasonable and avoids us +having to rebuild/download all packages. + +The implementation will read the `narinfo` for each store path in the cache at a +given commit and create a JSON-file containing the total reference count per +package. + +For the public Nixery instance, these popularity files will be distributed via a +GCS bucket. + +TIP: This is implemented in [popcount][] in Nixery. + +-------- + +Hopefully this detailed design review was useful to you. You can also watch [my +NixCon talk][talk] about Nixery for a review of some of this, and some demos. + +[Nixery]: https://github.com/google/nixery +[grhmc]: https://grahamc.com/blog/nix-and-layered-docker-images +[Nix]: https://nixos.org/nix +[registry protocols]: https://github.com/opencontainers/distribution-spec/blob/master/spec.md +[nixery.dev]: https://nixery.dev +[dominator trees]: https://en.wikipedia.org/wiki/Dominator_(graph_theory) +[gonum/graph]: https://godoc.org/gonum.org/v1/gonum/graph +[layers.go]: https://github.com/google/nixery/blob/master/builder/layers.go +[popcount]: https://github.com/google/nixery/tree/master/popcount +[talk]: https://www.youtube.com/watch?v=pOI9H4oeXqA diff --git a/users/tazjin/blog/posts/nsa-zettabytes.md b/users/tazjin/blog/posts/nsa-zettabytes.md new file mode 100644 index 000000000000..f8b326f2fb42 --- /dev/null +++ b/users/tazjin/blog/posts/nsa-zettabytes.md @@ -0,0 +1,93 @@ +I've been reading a few discussions on Reddit about the new NSA data +centre that is being built and stumbled upon [this +post](http://www.reddit.com/r/restorethefourth/comments/1jf6cx/the_guardian_releases_another_leaked_document_nsa/cbe5hnc), +putting its alleged storage capacity at *5 zettabytes*. + +That seems to be a bit much which I tried to explain to that guy, but I +was quickly blocked by the common conspiracy argument that government +technology is somehow far beyond the wildest dreams of us mere mortals - +thus I wrote a very long reply that will most likely never be seen by +anybody. Therefore I've decided to repost it here. + +------------------------------------------------------------------------ + +I feel like I've entered /r/conspiracy. Please have some facts (and do +read them!) + +A one terabyte SSD (I assume that\'s what you meant by flash-drive) +would require 5000000000 of those. That is *five billion* of those flash +drives. Can you visualise how much five billion flash-drives are? + +A single SSD is roughly 2cm\*13cm\*13cm with an approximate weight of +80g. That would make 400 000 metric tons of SSDs, a weight equivalent to +*over one thousand Boeing 747 airplanes*. Even if we assume that they +solder the flash chips directly onto some kind of controller (which also +weighs something), the raw material for that would be completely insane. + +Another visualization: If you stacked 5 billion SSDs on top of each +other you would get an SSD tower that is a hundred thousand kilometres +high, that is equivalent to 2,5 x the equatorial circumference of +*Earth* or 62000 miles. + +The volume of those SSDs would be clocking in at 1690000000 cubic +metres, more than the Empire State building. Are you still with me? + +Lets speak cost. The Samsung SSD that I assume you are referring to will +clock in at \$600, lets assume that the NSA gets a discount when buying +*five billion* of those and gets them at the cheap price of \$250. That +makes 1.25 trillion dollars. That would be a significant chunk of the +current US national debt. + +And all of this is just SSDs to stick into servers and storage units, +which need a whole bunch of other equipment as well to support them - +the cost would probably shoot up to something like 8 trillion dollars if +they were to build this. It would with very high certainty be more than +the annual production of SSDs (I can\'t find numbers on that +unfortunately) and take up *slightly* more space than they have in the +Utah data centre (assuming you\'re not going to tell me that it is in +fact attached to an underground base that goes down to the core of the +Earth). + +Lets look at the \"But the government has better technologies!\" idea. + +Putting aside the fact that the military *most likely* does not have a +secret base on Mars that deals with advanced science that the rest of us +can only dream of, and doing this under the assumption that they do have +this base, lets assume that they build a storage chip that stores 100TB. +This reduces the amount of needed chips to \"just\" 50 million, lets say +they get 10 of those into a server / some kind of specialized storage +unit and we only need 5 million of those specially engineered servers, +with custom connectors, software, chips, storage, most likely also power +sources and whatever - 10 million completely custom units built with +technology that is not available to the market. Google is estimated to +have about a million servers in total, I don\'t know exactly in how many +data centres those are placed but numbers I heard recently said that +it\'s about 40. When Apple assembles a new iPhone model they need +massive factories with thousands of workers and supplies from many +different countries, over several months, to assemble just a few million +units for their launch month. + +You are seriously proposing that the NSA is better than Google and Apple +and the rest of the tech industry, world-wide, combined at designing +*everything* in tech, manufacturing *everything* in tech, without *any* +information about that leaking and without *any* of the science behind +it being known? That\'s not just insane, that\'s outright impossible. + +And we haven\'t even touched upon how they would route the necessary +amounts of bandwidth (crazy insane) to save *the entire internet* into +that data center. + +------------------------------------------------------------------------ + +I\'m not saying that the NSA is not building a data center to store +surveillance information, to have more capacity to spy on people and all +that - I\'m merely making the point that the extent in which conspiracy +sites say they do this vastly overestimates their actual abilities. They +don\'t have magic available to them! Instead of making up insane figures +like that you should focus on what we actually know about their +operations, because using those figures in a debate with somebody who is +responsible for this (and knows what they\'re talking about) will end +with you being destroyed - nobody will listen to the rest of what +you\'re saying when that happens. + +\"Stick to the facts\" is valid for our side as well. diff --git a/users/tazjin/blog/posts/reversing-watchguard-vpn.md b/users/tazjin/blog/posts/reversing-watchguard-vpn.md new file mode 100644 index 000000000000..8968dc864590 --- /dev/null +++ b/users/tazjin/blog/posts/reversing-watchguard-vpn.md @@ -0,0 +1,158 @@ +TIP: WatchGuard has +[responded](https://www.reddit.com/r/netsec/comments/5tg0f9/reverseengineering_watchguard_mobile_vpn/dds6knx/) +to this post on Reddit. If you haven\'t read the post yet I\'d recommend +doing that first before reading the response to have the proper context. + +------------------------------------------------------------------------ + +One of my current clients makes use of +[WatchGuard](http://www.watchguard.com/help/docs/fireware/11/en-US/Content/en-US/mvpn/ssl/mvpn_ssl_client-install_c.html) +Mobile VPN software to provide access to the internal network. + +Currently WatchGuard only provides clients for OS X and Windows, neither +of which I am very fond of. In addition an OpenVPN configuration file is +provided, but it quickly turned out that this was only a piece of the +puzzle. + +The problem is that this VPN setup is secured using 2-factor +authentication (good!), but it does not use OpenVPN's default +[challenge/response](https://openvpn.net/index.php/open-source/documentation/miscellaneous/79-management-interface.html) +functionality to negotiate the credentials. + +Connecting with the OpenVPN config that the website supplied caused the +VPN server to send me a token to my phone, but I simply couldn't figure +out how to supply it back to the server. In a normal challenge/response +setting the token would be supplied as the password on the second +authentication round, but the VPN server kept rejecting that. + +Other possibilities were various combinations of username&password +(I've seen a lot of those around) so I tried a whole bunch, for example +`$password:$token` or even a `sha1(password, token)` - to no avail. + +At this point it was time to crank out +[Hopper](https://www.hopperapp.com/) and see what's actually going on +in the official OS X client - which uses OpenVPN under the hood! + +Diving into the client +---------------------- + +The first surprise came up right after opening the executable: It had +debug symbols in it - and was written in Objective-C! + +![Debug symbols](/static/img/watchblob_1.webp) + +A good first step when looking at an application binary is going through +the strings that are included in it, and the WatchGuard client had a lot +to offer. Among the most interesting were a bunch of URIs that looked +important: + +![Some URIs](/static/img/watchblob_2.webp) + +I started with the first one + + %@?action=sslvpn_download&filename=%@&fw_password=%@&fw_username=%@ + +and just curled it on the VPN host, replacing the username and +password fields with bogus data and the filename field with +`client.wgssl` - another string in the executable that looked like a +filename. + +To my surprise this endpoint immediately responded with a GZIPed file +containing the OpenVPN config, CA certificate, and the client +*certificate and key*, which I previously thought was only accessible +after logging in to the web UI - oh well. + +The next endpoint I tried ended up being a bit more interesting still: + + /?action=sslvpn_logon&fw_username=%@&fw_password=%@&style=fw_logon_progress.xsl&fw_logon_type=logon&fw_domain=Firebox-DB + +Inserting the correct username and password into the query parameters +actually triggered the process that sent a token to my phone. The +response was a simple XML blob: + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<resp> + <action>sslvpn_logon</action> + <logon_status>4</logon_status> + <auth-domain-list> + <auth-domain> + <name>RADIUS</name> + </auth-domain> + </auth-domain-list> + <logon_id>441</logon_id> + <chaStr>Enter Your 6 Digit Passcode </chaStr> +</resp> +``` + +Somewhat unsurprisingly that `chaStr` field is actually the challenge +string displayed in the client when logging in. + +This was obviously going in the right direction so I proceeded to the +procedures making use of this string. The first step was a relatively +uninteresting function called `-[VPNController sslvpnLogon]` which +formatted the URL, opened it and checked whether the `logon_status` was +`4` before proceeding with the `logon_id` and `chaStr` contained in the +response. + +*(Code snippets from here on are Hopper's pseudo-Objective-C)* + +![sslvpnLogon](/static/img/watchblob_3.webp) + +It proceeded to the function `-[VPNController processTokenPrompt]` which +showed the dialog window into which the user enters the token, sent it +off to the next URL and checked the `logon_status` again: + +(`r12` is the reference to the `VPNController` instance, i.e. `self`). + +![processTokenPrompt](/static/img/watchblob_4.webp) + +If the `logon_status` was `1` (apparently \"success\" here) it proceeded +to do something quite interesting: + +![processTokenPrompt2](/static/img/watchblob_5.webp) + +The user's password was overwritten with the (verified) OTP token - +before OpenVPN had even been started! + +Reading a bit more of the code in the subsequent +`-[VPNController doLogin]` method revealed that it shelled out to +`openvpn` and enabled the management socket, which makes it possible to +remotely control an `openvpn` process by sending it commands over TCP. + +It then simply sent the username and the OTP token as the credentials +after configuring OpenVPN with the correct config file: + +![doLogin](/static/img/watchblob_6.webp) + +... and the OpenVPN connection then succeeds. + +TL;DR +----- + +Rather than using OpenVPN's built-in challenge/response mechanism, the +WatchGuard client validates user credentials *outside* of the VPN +connection protocol and then passes on the OTP token, which seems to be +temporarily in a 'blessed' state after verification, as the user's +password. + +I didn't check to see how much verification of this token is performed +(does it check the source IP against the IP that performed the challenge +validation?), but this certainly seems like a bit of a security issue - +considering that an attacker on the same network would, if they time the +attack right, only need your username and 6-digit OTP token to +authenticate. + +Don't roll your own security, folks! + +Bonus +----- + +The whole reason why I set out to do this is so I could connect to this +VPN from Linux, so this blog post wouldn't be complete without a +solution for that. + +To make this process really easy I've written a [little +tool](https://github.com/tazjin/watchblob) that performs the steps +mentioned above from the CLI and lets users know when they can +authenticate using their OTP token. diff --git a/users/tazjin/blog/posts/sick-in-sweden.md b/users/tazjin/blog/posts/sick-in-sweden.md new file mode 100644 index 000000000000..0c43c5832d73 --- /dev/null +++ b/users/tazjin/blog/posts/sick-in-sweden.md @@ -0,0 +1,26 @@ +I\'ve been sick more in the two years in Sweden than in the ten years +before that. + +Why? I have a theory about it and after briefly discussing it with one +of my roommates (who is experiencing the same thing) I\'d like to share +it with you: + +Normally when people get sick, are coughing, have a fever and so on they +take a few days off from work and stay at home. The reasons are twofold: +You want to rest a bit in order to get rid of the disease and you want +to *avoid infecting your co-workers*. + +In Sweden people will drag themselves into work anyways, because of a +concept called the +[karensdag](https://www.forsakringskassan.se/wps/portal/sjukvard/sjukskrivning_och_sjukpenning/karensdag_och_forstadagsintyg). +The TL;DR of this is \'if you take days off sick you won\'t get paid for +the first day, and only 80% of your salary on the remaining days\'. + +Many people are not willing to take that financial hit. In combination +with Sweden\'s rather mediocre healthcare system you end up constantly +being surrounded by sick people, not just in your own office but also on +public transport and basically all other public places. + +Oh and the best thing about this? Swedish politicians [often ignore +this](https://www.aftonbladet.se/nyheter/article10506886.ab) rule and +just don\'t report their sick days. Nice. diff --git a/users/tazjin/blog/posts/the-smu-problem.md b/users/tazjin/blog/posts/the-smu-problem.md new file mode 100644 index 000000000000..f411e3116046 --- /dev/null +++ b/users/tazjin/blog/posts/the-smu-problem.md @@ -0,0 +1,151 @@ +After having tested countless messaging apps over the years, being +unsatisfied with most of them and finally getting stuck with +[Telegram](https://telegram.org/) I have developed a little theory about +messaging apps. + +SMU stands for *Security*, *Multi-Device* and *Usability*. Quite like +the [CAP-theorem](https://en.wikipedia.org/wiki/CAP_theorem) I believe +that you can - using current models - only solve two out of three things +on this list. Let me elaborate what I mean by the individual points: + +**Security**: This is mainly about encryption of messages, not so much +about hiding identities to third-parties. Commonly some kind of +asymmetric encryption scheme. Verification of keys used must be possible +for the user. + +**Multi-Device**: Messaging-app clients for multiple devices, with +devices being linked to the same identifier, receiving the same messages +and being independent of each other. A nice bonus is also an open +protocol (like Telegram\'s) that would let people write new clients. + +**Usability**: Usability is a bit of a broad term, but what I mean by it +here is handling contacts and identities. It should be easy to create +accounts, give contact information to people and have everything just +work in a somewhat automated fashion. + +Some categorisation of popular messaging apps: + +**SU**: Threema + +**MU**: Telegram, Google Hangouts, iMessage, Facebook Messenger + +**SM**: +[Signal](https://gist.github.com/TheBlueMatt/d2fcfb78d29faca117f5) + +*Side note: The most popular messaging app - WhatsApp - only scores a +single letter (U). This makes it completely uninteresting to me.* + +Let\'s talk about **SM** - which might contain the key to solving SMU. +Two approaches are interesting here. + +The single key model +-------------------- + +In Signal there is a single identity key which can be used to register a +device on the server. There exists a process for sharing this identity +key from a primary device to a secondary one, so that the secondary +device can register itself (see the link above for a description). + +This *almost* breaks M because there is still a dependence on a primary +device and newly onboarded devices can not be used to onboard further +devices. However, for lack of a better SM example I\'ll give it a pass. + +The other thing it obviously breaks is U as the process for setting it +up is annoying and having to rely on the primary device is a SPOF (there +might be a way to recover from a lost primary device, but I didn\'t find +any information so far). + +The multiple key model +---------------------- + +In iMessage every device that a user logs into creates a new key pair +and submits its public key to a per-account key pool. Senders fetch all +available public keys for a recipient and encrypt to all of the keys. + +Devices that join can catch up on history by receiving it from other +devices that use its public key. + +This *almost* solves all of SMU, but its compliance with S breaks due to +the fact that the key pool is not auditable, and controlled by a +third-party (Apple). How can you verify that they don\'t go and add +another key to your pool? + +A possible solution +------------------- + +Out of these two approaches I believe the multiple key one looks more +promising. If there was a third-party handling the key pool but in a way +that is verifiable, transparent and auditable that model could be used +to solve SMU. + +The technology I have been thinking about for this is some kind of +blockchain model and here\'s how I think it could work: + +1. Bob installs the app and begins onboarding. The first device + generates its keypair, submits the public key and an account + creation request. + +2. Bob\'s account is created on the messaging apps\' servers and a + unique identifier plus the fingerprint of the first device\'s public + key is written to the chain. + +3. Alice sends a message to Bob, her device asks the messaging service + for Bob\'s account\'s identity and public keys. Her device verifies + the public key fingerprint against the one in the blockchain before + encrypting to it and sending the message. + +4. Bob receives Alice\'s message on his first device. + +5. Bob logs in to his account on a second device. The device generates + a key pair and sends the public key to the service, the service + writes it to the blockchain using its identifier. + +6. The messaging service requests that Bob\'s first device signs the + second device\'s key and triggers a simple confirmation popup. + +7. Bob confirms the second device on his first device. It signs the key + and writes the signature to the chain. + +8. Alice sends another message, her device requests Bob\'s current keys + and receives the new key. It verifies that both the messaging + service and one of Bob\'s older devices have confirmed this key in + the chain. It encrypts the message to both keys and sends it on. + +9. Bob receives Alice\'s message on both devices. + +After this the second device can request conversation history from the +first one to synchronise old messages. + +Further devices added to an account can be confirmed by any of the +devices already in the account. + +The messaging service could not add new keys for an account on its own +because it does not control any of the private keys confirmed by the +chain. + +In case all devices were lost, the messaging service could associate the +account with a fresh identity in the block chain. Message history +synchronisation would of course be impossible. + +Feedback welcome +---------------- + +I would love to hear some input on this idea, especially if anyone knows +of an attempt to implement a similar model already. Possible attack +vectors would also be really interesting. + +Until something like this comes to fruition, I\'ll continue using +Telegram with GPG as the security layer when needed. + +**Update:** WhatsApp has launched an integration with the Signal guys +and added their protocol to the official WhatsApp app. This means +WhatsApp now firmly sits in the SU-category, but it still does not solve +this problem. + +**Update 2:** Facebook Messenger has also integrated with Signal, but +their secret chats do not support multi-device well (it is Signal +afterall). This means it scores either SU or MU depending on which mode +you use it in. + +An interesting service I have not yet evaluated properly is +[Matrix](http://matrix.org/). diff --git a/users/tazjin/covid/us_mortality.jq b/users/tazjin/covid/us_mortality.jq new file mode 100644 index 000000000000..584be3ef9afe --- /dev/null +++ b/users/tazjin/covid/us_mortality.jq @@ -0,0 +1,36 @@ +# This turns the CDC mortality data[0] into a format useful for my +# excess mortality spreadsheet. The US format is by far the worst one +# I have dealt with, as expected. +# +# This requires miller for transforming the CSV appropriately. +# +# Params: +# state: abbreviation of the state to extract ('US' for whole country) +# period: time period (either "2020" for current data, or anything else +# for historical averages) +# +# Call as: +# mlr --icsv --ojson cat weekly.csv | \ +# jq -rsf us_mortality.jq --arg state US --arg period 2020 +# +# [0]: https://www.cdc.gov/nchs/nvss/vsrr/covid19/excess_deaths.htm + +def filter_period(period): + if period == "2020" + then . | map(select(.["Time Period"] == 2020)) + else . | map(select(.["Time Period"] == "2015-2019")) + end; + +def collate_weeks(period): + (. | map(.["Number of Deaths"]) | add) as $count + | { + count: (if period == "2020" then $count else $count / 5 end), + week: .[0].Week, + }; + +. | map(select(.Type == "Predicted (weighted)")) + | map(select(.["State Abbreviation"] == $state)) + | filter_period($period) + | group_by(.Week) + | map(collate_weeks($period)) + | .[] | "week \(.week): \(.count)" diff --git a/users/tazjin/dns/default.nix b/users/tazjin/dns/default.nix new file mode 100644 index 000000000000..da92b88beade --- /dev/null +++ b/users/tazjin/dns/default.nix @@ -0,0 +1,12 @@ +# Performs simple (local-only) validity checks on DNS zones. +{ depot, pkgs, ... }: + +let + checkZone = zone: file: pkgs.runCommandNoCC "${zone}-check" {} '' + ${pkgs.bind}/bin/named-checkzone -i local ${zone} ${file} | tee $out + ''; + +in depot.nix.readTree.drvTargets { + kontemplate-works = checkZone "kontemplate.works"./kontemplate.works.zone; + tazj-in = checkZone "tazj.in" ./tazj.in.zone; +} diff --git a/users/tazjin/dns/import b/users/tazjin/dns/import new file mode 100755 index 000000000000..8ea1d694c9a1 --- /dev/null +++ b/users/tazjin/dns/import @@ -0,0 +1,12 @@ +#!/bin/sh +set -ue + +# Imports a zone file into Google Cloud DNS +readonly ZONE="${1}" +readonly FILE="${2}" + +gcloud dns record-sets import "${FILE}" \ + --project composite-watch-759 \ + --zone-file-format \ + --delete-all-existing \ + --zone "${ZONE}" diff --git a/users/tazjin/dns/kontemplate.works.zone b/users/tazjin/dns/kontemplate.works.zone new file mode 100644 index 000000000000..326a129d2105 --- /dev/null +++ b/users/tazjin/dns/kontemplate.works.zone @@ -0,0 +1,15 @@ +;; -*- mode: zone; -*- +;; Do not delete these +kontemplate.works. 21600 IN NS ns-cloud-d1.googledomains.com. +kontemplate.works. 21600 IN NS ns-cloud-d2.googledomains.com. +kontemplate.works. 21600 IN NS ns-cloud-d3.googledomains.com. +kontemplate.works. 21600 IN NS ns-cloud-d4.googledomains.com. +kontemplate.works. 21600 IN SOA ns-cloud-d1.googledomains.com. cloud-dns-hostmaster.google.com. 4 21600 3600 259200 300 + +;; Github site setup +kontemplate.works. 60 IN A 185.199.108.153 +kontemplate.works. 60 IN A 185.199.109.153 +kontemplate.works. 60 IN A 185.199.110.153 +kontemplate.works. 60 IN A 185.199.111.153 + +www.kontemplate.works. 60 IN CNAME tazjin.github.io. diff --git a/users/tazjin/dns/tazj.in.zone b/users/tazjin/dns/tazj.in.zone new file mode 100644 index 000000000000..43db5834a0ca --- /dev/null +++ b/users/tazjin/dns/tazj.in.zone @@ -0,0 +1,33 @@ +;; -*- mode: zone; -*- +;; Do not delete these +tazj.in. 21600 IN NS ns-cloud-a1.googledomains.com. +tazj.in. 21600 IN NS ns-cloud-a2.googledomains.com. +tazj.in. 21600 IN NS ns-cloud-a3.googledomains.com. +tazj.in. 21600 IN NS ns-cloud-a4.googledomains.com. +tazj.in. 21600 IN SOA ns-cloud-a1.googledomains.com. cloud-dns-hostmaster.google.com. 123 21600 3600 1209600 300 + +;; Email setup +tazj.in. 300 IN MX 1 aspmx.l.google.com. +tazj.in. 300 IN MX 5 alt1.aspmx.l.google.com. +tazj.in. 300 IN MX 5 alt2.aspmx.l.google.com. +tazj.in. 300 IN MX 10 alt3.aspmx.l.google.com. +tazj.in. 300 IN MX 10 alt4.aspmx.l.google.com. +tazj.in. 300 IN TXT "v=spf1 include:_spf.google.com ~all" +google._domainkey.tazj.in. 21600 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9AphX/WJf8zVXQB5Jk0Ry1MI6ARa6vEyAoJtpjpt9Nbm7XU4qVWFRJm+L0VFd5EZ5YDPJTIZ90lJE3/B8vae2ipnoGbJbj8LaVSzzIPMbWmhPhX3fkLJFdkv7xRDMDn730iYXRlfkgv6GsqbS8vZt7mzxx4mpnePTI323yjRVkwRW8nGVbsmB25ZoG1/0985" "kg4mSYxzWeJ2ozCPFhT4sfMtZMXe/4QEkJz/zkod29KZfFJmLgEaf73WLdBX8kdwbhuh2PYXt/PwzUrRzF5ujVCsSaTZwdRVPErcf+yo4NvedelTjjs8rFVfoJiaDD1q2bQ3w0gDEBWPdC2VP7k9zwIDAQAB" + +;; Site verifications +tazj.in. 3600 IN TXT "keybase-site-verification=gC4kzEmnLzY7F669PjN-pw2Cf__xHqcxQ08Gb-W9dhE" +tazj.in. 300 IN TXT "google-site-verification=d3_MI1OwD6q2OT42Vvh0I9w2u3Q5KFBu-PieNUE1Fig" +www.tazj.in. 3600 IN TXT "keybase-site-verification=ER8m_byyqAhzeIy9TyzkAU1H2p2yHtpvImuB_XrRF2U" + +;; Blog "storage engine" +blog.tazj.in. 21600 IN NS ns-cloud-c1.googledomains.com. +blog.tazj.in. 21600 IN NS ns-cloud-c2.googledomains.com. +blog.tazj.in. 21600 IN NS ns-cloud-c3.googledomains.com. +blog.tazj.in. 21600 IN NS ns-cloud-c4.googledomains.com. + +;; Webpage records setup +tazj.in. 300 IN A 34.98.120.189 +www.tazj.in. 300 IN A 34.98.120.189 +git.tazj.in. 300 IN A 34.98.120.189 +files.tazj.in. 300 IN CNAME c.storage.googleapis.com. diff --git a/users/tazjin/dotfiles/config.fish b/users/tazjin/dotfiles/config.fish new file mode 100644 index 000000000000..de2c99ae6007 --- /dev/null +++ b/users/tazjin/dotfiles/config.fish @@ -0,0 +1,40 @@ +# Configure classic prompt +set fish_color_user --bold blue +set fish_color_cwd --bold white + +# Enable colour hints in VCS prompt: +set __fish_git_prompt_showcolorhints yes +set __fish_git_prompt_color_prefix purple +set __fish_git_prompt_color_suffix purple + +# Fish configuration +set fish_greeting "" +set PATH $HOME/.local/bin $HOME/.cargo/bin $PATH + +# Editor configuration +set -gx EDITOR "emacsclient" +set -gx ALTERNATE_EDITOR "emacs -q -nw" +set -gx VISUAL "emacsclient" + +# Miscellaneous +eval (direnv hook fish) + +# Useful command aliases +alias gpr 'git pull --rebase' +alias gco 'git checkout' +alias gf 'git fetch' +alias gap 'git add -p' +alias pbcopy 'xclip -selection clipboard' +alias edit 'emacsclient -n' +alias servedir 'nix-shell -p haskellPackages.wai-app-static --run warp' + +# Old habits die hard (also ls is just easier to type): +alias ls 'exa' + +# Fix up nix-env & friends for Nix 2.0 +export NIX_REMOTE=daemon + +# Fix display of fish in emacs' term-mode: +function fish_title + true +end diff --git a/users/tazjin/dotfiles/default.nix b/users/tazjin/dotfiles/default.nix new file mode 100644 index 000000000000..9b783a9c857c --- /dev/null +++ b/users/tazjin/dotfiles/default.nix @@ -0,0 +1,3 @@ +_: { + dunstrc = ./dunstrc; +} diff --git a/users/tazjin/dotfiles/dunstrc b/users/tazjin/dotfiles/dunstrc new file mode 100644 index 000000000000..2aa1141b6ec2 --- /dev/null +++ b/users/tazjin/dotfiles/dunstrc @@ -0,0 +1,54 @@ +[global] +font = Iosevka Term 11 +origin = top-left +markup = yes +plain_text = no +format = "<b>%s</b>\n%b" +sort = no +indicate_hidden = yes +alignment = center +bounce_freq = 0 +show_age_threshold = -1 +word_wrap = yes +ignore_newline = no +stack_duplicates = yes +hide_duplicate_count = yes +geometry = "300x50-15+49" +shrink = no +transparency = 5 +idle_threshold = 0 +monitor = 0 +follow = keyboard +sticky_history = yes +history_length = 15 +show_indicators = no +line_height = 3 +separator_height = 2 +padding = 6 +horizontal_padding = 6 +separator_color = frame +startup_notification = false +dmenu = /usr/bin/dmenu -p dunst: +browser = /usr/bin/firefox -new-tab +icon_position = off +max_icon_size = 80 +frame_width = 3 +frame_color = "#8EC07C" + +[urgency_low] +frame_color = "#3B7C87" +foreground = "#3B7C87" +background = "#191311" +timeout = 4 + +[urgency_normal] +frame_color = "#5B8234" +foreground = "#5B8234" +background = "#191311" +timeout = 6 + +[urgency_critical] +frame_color = "#B7472A" +foreground = "#B7472A" +background = "#191311" +timeout = 8 diff --git a/users/tazjin/dotfiles/msmtprc b/users/tazjin/dotfiles/msmtprc new file mode 100644 index 000000000000..2af3b9433a6d --- /dev/null +++ b/users/tazjin/dotfiles/msmtprc @@ -0,0 +1,15 @@ +defaults +port 587 +tls on +tls_trust_file /etc/ssl/certs/ca-certificates.crt + +# GSuite for tazj.in +account tazjin +host smtp.gmail.com +port 587 +from mail@tazj.in +auth oauthbearer +user mail@tazj.in +passwordeval "cat ~/mail/account.tazjin/.credentials.gmailieer.json | jq -r '.access_token'" + +account default : tazjin diff --git a/users/tazjin/dotfiles/notmuch-config b/users/tazjin/dotfiles/notmuch-config new file mode 100644 index 000000000000..a490774e635f --- /dev/null +++ b/users/tazjin/dotfiles/notmuch-config @@ -0,0 +1,21 @@ +# .notmuch-config - Configuration file for the notmuch mail system +# +# For more information about notmuch, see https://notmuchmail.org + +[database] +path=/home/vincent/mail + +[user] +name=Vincent Ambo +primary_email=mail@tazj.in +other_email=tazjin@gmail.com; + +[new] +tags=unread;inbox; +ignore= + +[search] +exclude_tags=deleted;spam;draft; + +[maildir] +synchronize_flags=true diff --git a/users/tazjin/dt/CMakeLists.txt b/users/tazjin/dt/CMakeLists.txt new file mode 100644 index 000000000000..85b659fea862 --- /dev/null +++ b/users/tazjin/dt/CMakeLists.txt @@ -0,0 +1,16 @@ +# -*- mode: cmake; -*- +cmake_minimum_required(VERSION 3.16) +project(dt) +add_executable(dt dt.cc) +find_package(absl REQUIRED) + +target_link_libraries(dt + absl::flags + absl::flags_parse + absl::hash + absl::time + absl::strings + farmhash +) + +install(TARGETS dt DESTINATION bin) diff --git a/users/tazjin/dt/README.md b/users/tazjin/dt/README.md new file mode 100644 index 000000000000..ee43d5606409 --- /dev/null +++ b/users/tazjin/dt/README.md @@ -0,0 +1,11 @@ +dt +== + +It's got a purpose. + +## Usage: + +``` +nix-build -E '(import (builtins.fetchGit "https://git.tazj.in/") {}).fun.dt' +./result/bin/dt --one ... --two ... +``` diff --git a/users/tazjin/dt/default.nix b/users/tazjin/dt/default.nix new file mode 100644 index 000000000000..8a728062db21 --- /dev/null +++ b/users/tazjin/dt/default.nix @@ -0,0 +1,13 @@ +{ depot, pkgs, ... }: + +let stdenv = with pkgs; overrideCC clangStdenv clang_11; +in stdenv.mkDerivation { + name = "dt"; + src = ./.; + nativeBuildInputs = [ pkgs.cmake ]; + buildInputs = with depot.third_party; [ + abseil_cpp + farmhash + ]; + meta.ci = false; +} diff --git a/users/tazjin/dt/dt.cc b/users/tazjin/dt/dt.cc new file mode 100644 index 000000000000..5c4c3da76853 --- /dev/null +++ b/users/tazjin/dt/dt.cc @@ -0,0 +1,79 @@ +#include <iostream> +#include <vector> + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/hash/hash.h" +#include "absl/strings/str_cat.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/optional.h" +#include "farmhash.h" + +ABSL_FLAG(std::vector<std::string>, words, {}, "words to use"); + +struct Result { + std::string a; + int ec; + absl::optional<std::string> p; +}; + +std::string which(const std::vector<std::string>& words) { + uint64_t fp; + std::string word; + + for (const auto& w : words) { + auto nfp = util::Fingerprint64(w); + if (nfp > fp) { + fp = nfp; + word = w; + } + } + + return word; +} + +Result decide(const std::vector<std::string>& words) { + auto input = absl::FormatTime("%Y%m%d", absl::Now(), absl::UTCTimeZone()); + for (const auto& w : words) { + input += w; + } + + auto base = util::Fingerprint64(input); + Result result = { "nope" }; + + if (base % 10 == 0) { + result.a = "ca"; + } else if (base % 8 == 0) { + result.a = "c1"; + result.p = which(words); + } else if (base % 6 == 0) { + result.a = "skip"; + } else if (base % 3 == 0) { + result.a = "e1"; + result.ec = base % 10; + result.p = which(words); + } else if (base % 2 == 0) { + result.a = "ea"; + result.ec = base % 10; + } + + return result; +} + +int main(int argc, char *argv[]) { + absl::ParseCommandLine(argc, argv); + + auto words = absl::GetFlag(FLAGS_words); + if (words.size() < 2) { + std::cerr << "needs at least two!" << std::endl; + return 1; + } + + auto result = decide(words); + std::cout << result.a + << (result.p.has_value() ? absl::StrCat(" ", "(", result.p.value(), ")") + : "") + << (result.ec > 0 ? absl::StrCat(": ", result.ec) : "") + << std::endl; +} diff --git a/users/tazjin/emacs/.gitignore b/users/tazjin/emacs/.gitignore new file mode 100644 index 000000000000..7b666905f847 --- /dev/null +++ b/users/tazjin/emacs/.gitignore @@ -0,0 +1,11 @@ +.smex-items +*token* +auto-save-list/ +clones/ +elpa/ +irc.el +local.el +other/ +scripts/ +themes/ +*.elc diff --git a/users/tazjin/emacs/README.md b/users/tazjin/emacs/README.md new file mode 100644 index 000000000000..5c667333962e --- /dev/null +++ b/users/tazjin/emacs/README.md @@ -0,0 +1,7 @@ +tools/emacs +=========== + +This sub-folder builds my Emacs configuration, supplying packages from +Nix and configuration from this folder. + +I use Emacs for many things (including as my desktop environment). diff --git a/users/tazjin/emacs/config/bindings.el b/users/tazjin/emacs/config/bindings.el new file mode 100644 index 000000000000..21cca06cc83d --- /dev/null +++ b/users/tazjin/emacs/config/bindings.el @@ -0,0 +1,62 @@ +;; Font size +(define-key global-map (kbd "C-=") 'increase-default-text-scale) ;; '=' because there lies '+' +(define-key global-map (kbd "C--") 'decrease-default-text-scale) +(define-key global-map (kbd "C-x C-0") 'set-default-text-scale) + +;; What does <tab> do? Well, it depends ... +(define-key prog-mode-map (kbd "<tab>") #'company-indent-or-complete-common) + +;; imenu instead of insert-file +(global-set-key (kbd "C-x i") 'imenu) + +;; Window switching. (C-x o goes to the next window) +(windmove-default-keybindings) ;; Shift+direction + +;; Start eshell or switch to it if it's active. +(global-set-key (kbd "C-x m") 'eshell) + +(global-set-key (kbd "C-x C-p") 'browse-repositories) +(global-set-key (kbd "M-g M-g") 'goto-line-with-feedback) + +;; Miscellaneous editing commands +(global-set-key (kbd "C-c w") 'whitespace-cleanup) +(global-set-key (kbd "C-c a") 'align-regexp) +(global-set-key (kbd "C-c m") 'mc/mark-dwim) + +;; Browse URLs (very useful for Gitlab's SSH output!) +(global-set-key (kbd "C-c b p") 'browse-url-at-point) +(global-set-key (kbd "C-c b b") 'browse-url) + +;; C-x REALLY QUIT (idea by @magnars) +(global-set-key (kbd "C-x r q") 'save-buffers-kill-terminal) +(global-set-key (kbd "C-x C-c") 'ignore) + +;; Open a file in project: +(global-set-key (kbd "C-c f") 'project-find-file) + +;; Search in a project +(global-set-key (kbd "C-c r g") 'rg-in-project) + +;; Open a file via magit: +(global-set-key (kbd "C-c C-f") #'magit-find-file-worktree) + +;; Insert TODO comments +(global-set-key (kbd "C-c t") 'insert-todo-comment) + +;; Make sharing music easier +(global-set-key (kbd "s-s w") #'songwhip-lookup-url) + +;; Open the depot +(global-set-key (kbd "s-s d") #'tvl-depot-status) + +;; Add subthread collapsing to notmuch-show. +;; +;; C-, closes a thread, C-. opens a thread. This mirrors stepping +;; in/out of definitions. +(define-key notmuch-show-mode-map (kbd "C-,") 'notmuch-show-open-or-close-subthread) +(define-key notmuch-show-mode-map (kbd "C-.") + (lambda () + (interactive) + (notmuch-show-open-or-close-subthread t))) ;; open + +(provide 'bindings) diff --git a/users/tazjin/emacs/config/custom.el b/users/tazjin/emacs/config/custom.el new file mode 100644 index 000000000000..2bb7ad489600 --- /dev/null +++ b/users/tazjin/emacs/config/custom.el @@ -0,0 +1,25 @@ +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(ac-auto-show-menu 0.8) + '(ac-delay 0.2) + '(avy-background t) + '(cargo-process--enable-rust-backtrace 1) + '(company-auto-complete (quote (quote company-explicit-action-p))) + '(company-idle-delay 0.5) + '(custom-enabled-themes (quote (gruber-darker))) + '(display-time-default-load-average nil) + '(display-time-interval 30) + '(elnode-send-file-program "/run/current-system/sw/bin/cat") + '(frame-brackground-mode (quote dark)) + '(global-auto-complete-mode t) + '(kubernetes-commands-display-buffer-function (quote display-buffer)) + '(lsp-gopls-server-path "/home/tazjin/go/bin/gopls") + '(magit-log-show-gpg-status t) + '(ns-alternate-modifier (quote none)) + '(ns-command-modifier (quote control)) + '(ns-right-command-modifier (quote meta)) + '(require-final-newline (quote visit-save)) + '(tls-program (quote ("gnutls-cli --x509cafile %t -p %p %h")))) diff --git a/users/tazjin/emacs/config/desktop.el b/users/tazjin/emacs/config/desktop.el new file mode 100644 index 000000000000..3a4983f629f0 --- /dev/null +++ b/users/tazjin/emacs/config/desktop.el @@ -0,0 +1,261 @@ +;; -*- lexical-binding: t; -*- +;; +;; Configure desktop environment settings, including both +;; window-management (EXWM) as well as additional system-wide +;; commands. + +(require 's) +(require 'f) +(require 'dash) +(require 'exwm) +(require 'exwm-config) +(require 'exwm-randr) +(require 'exwm-systemtray) + +(defcustom tazjin--screen-lock-command "tazjin-screen-lock" + "Command to execute for locking the screen." + :group 'tazjin) + +(defcustom tazjin--backlight-increase-command "light -A 4" + "Command to increase screen brightness." + :group 'tazjin) + +(defcustom tazjin--backlight-decrease-command "light -U 4" + "Command to decrease screen brightness." + :group 'tazjin) + +(defun pactl (cmd) + (shell-command (concat "pactl " cmd)) + (message "Volume command: %s" cmd)) + +(defun volume-mute () (interactive) (pactl "set-sink-mute @DEFAULT_SINK@ toggle")) +(defun volume-up () (interactive) (pactl "set-sink-volume @DEFAULT_SINK@ +5%")) +(defun volume-down () (interactive) (pactl "set-sink-volume @DEFAULT_SINK@ -5%")) + +(defun brightness-up () + (interactive) + (shell-command tazjin--backlight-increase-command) + (message "Brightness increased")) + +(defun brightness-down () + (interactive) + (shell-command tazjin--backlight-decrease-command) + (message "Brightness decreased")) + +(defun set-xkb-layout (layout) + "Set the current X keyboard layout." + + (shell-command (format "setxkbmap %s" layout)) + (shell-command "setxkbmap -option caps:super") + (message "Set X11 keyboard layout to '%s'" layout)) + +(defun lock-screen () + (interactive) + (set-xkb-layout "us") + (shell-command tazjin--screen-lock-command)) + +(defun create-window-name () + "Construct window names to be used for EXWM buffers by + inspecting the window's X11 class and title. + + A lot of commonly used applications either create titles that + are too long by default, or in the case of web + applications (such as Cider) end up being constructed in + awkward ways. + + To avoid this issue, some rewrite rules are applied for more + human-accessible titles." + + (pcase (list (or exwm-class-name "unknown") (or exwm-title "unknown")) + ;; In Cider windows, rename the class and keep the workspace/file + ;; as the title. + (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider" title))) title)) + (format "Cider<%s>" (s-chop-suffix " - Cider" title))) + (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider V" title))) title)) + (format "Cider V<%s>" (s-chop-suffix " - Cider V" title))) + + ;; Attempt to detect IRCCloud windows via their title, which is a + ;; combination of the channel name and network. + ;; + ;; This is what would often be referred to as a "hack". The regexp + ;; will not work if a network connection buffer is selected in + ;; IRCCloud, but since the title contains no other indication that + ;; we're dealing with an IRCCloud window + (`("Google-chrome" + ,(and (pred (lambda (title) + (s-matches? "^[\*\+]\s#[a-zA-Z0-9/\-]+\s\|\s[a-zA-Z\.]+$" title))) + title)) + (format "IRCCloud<%s>" title)) + + ;; For other Chrome windows, make the title shorter. + (`("Google-chrome" ,title) + (format "Chrome<%s>" (s-truncate 42 (s-chop-suffix " - Google Chrome" title)))) + + ;; Gnome-terminal -> Term + (`("Gnome-terminal" ,title) + ;; fish-shell buffers contain some unnecessary whitespace and + ;; such before the current working directory. This can be + ;; stripped since most of my terminals are fish shells anyways. + (format "Term<%s>" (s-trim-left (s-chop-prefix "fish" title)))) + + ;; Quassel buffers + ;; + ;; These have a title format that looks like: + ;; "Quassel IRC - #tvl (hackint) โ Quassel IRC" + (`("quassel" ,title) + (progn + (if (string-match + (rx "Quassel IRC - " + (group (one-or-more (any alnum "[" "]" "&" "-" "#"))) ;; <-- channel name + " (" (group (one-or-more (any ascii space))) ")" ;; <-- network name + " โ Quassel IRC") + title) + (format "Quassel<%s>" (match-string 2 title)) + title))) + + ;; For any other application, a name is constructed from the + ;; window's class and name. + (`(,class ,title) (format "%s<%s>" class (s-truncate 12 title))))) + +;; EXWM launch configuration +;; +;; This used to use use-package, but when something breaks use-package +;; it doesn't exactly make debugging any easier. + +(let ((titlef (lambda () + (exwm-workspace-rename-buffer (create-window-name))))) + (add-hook 'exwm-update-class-hook titlef) + (add-hook 'exwm-update-title-hook titlef)) + +(fringe-mode 3) +(exwm-enable) + +;; 's-N': Switch to certain workspace +(setq exwm-workspace-number 10) +(dotimes (i 10) + (exwm-input-set-key (kbd (format "s-%d" i)) + `(lambda () + (interactive) + (exwm-workspace-switch-create ,i)))) + +;; Launch applications / any command with completion (dmenu style!) +(exwm-input-set-key (kbd "s-d") #'counsel-linux-app) +(exwm-input-set-key (kbd "s-x") #'run-external-command) +(exwm-input-set-key (kbd "s-p") #'password-store-lookup) + +;; Add X11 terminal selector to a key +(exwm-input-set-key (kbd "C-x t") #'ts/switch-to-terminal) + +;; Toggle between line-mode / char-mode +(exwm-input-set-key (kbd "C-c C-t C-t") #'exwm-input-toggle-keyboard) + +;; Volume keys +(exwm-input-set-key (kbd "<XF86AudioMute>") #'volume-mute) +(exwm-input-set-key (kbd "<XF86AudioRaiseVolume>") #'volume-up) +(exwm-input-set-key (kbd "<XF86AudioLowerVolume>") #'volume-down) + +;; Brightness keys +(exwm-input-set-key (kbd "<XF86MonBrightnessDown>") #'brightness-down) +(exwm-input-set-key (kbd "<XF86MonBrightnessUp>") #'brightness-up) +(exwm-input-set-key (kbd "<XF86Display>") #'lock-screen) + +;; Shortcuts for switching between keyboard layouts +(defmacro bind-xkb (lang key) + `(exwm-input-set-key (kbd (format "s-%s" ,key)) + (lambda () + (interactive) + (set-xkb-layout ,lang)))) + +(bind-xkb "us" "k u") +(bind-xkb "de" "k d") +(bind-xkb "no" "k n") +(bind-xkb "ru" "k r") +(bind-xkb "se" "k s") + +;; These are commented out because Emacs no longer starts (??) if +;; they're set at launch. +;; +(bind-xkb "us" "ะป ะณ") +(bind-xkb "de" "ะป ะฒ") +(bind-xkb "no" "ะป ั") +(bind-xkb "ru" "ะป ะบ") + +;; Line-editing shortcuts +(exwm-input-set-simulation-keys + '(([?\C-d] . delete) + ([?\C-w] . ?\C-c))) + +;; Show time & battery status in the mode line +(display-time-mode) +(display-battery-mode) + +;; enable display of X11 system tray within Emacs +(exwm-systemtray-enable) + +;; Configure xrandr (multi-monitor setup). + +(defun set-randr-config (screens) + (setq exwm-randr-workspace-monitor-plist + (-flatten (-map (lambda (screen) + (-map (lambda (screen-id) (list screen-id (car screen))) (cdr screen))) + screens)))) + +;; Layouts for Tverskoy (X13 AMD laptop) +(defun randr-tverskoy-layout-single () + "Laptop screen only!" + (interactive) + (set-randr-config '(("eDP" (number-sequence 0 9)))) + (shell-command "xrandr --output eDP --auto --primary") + (shell-command "xrandr --output HDMI-A-0 --off") + (exwm-randr-refresh)) + +(defun randr-tverskoy-split-workspace () + "Split the workspace across two screens, assuming external to the left." + (interactive) + (set-randr-config + '(("HDMI-A-0" 1 2 3 4 5 6 7) + ("eDP" 8 9 0))) + + (shell-command "xrandr --output HDMI-A-0 --left-of eDP --auto") + (exwm-randr-refresh)) + +;; Layouts for frog (desktop) + +(defun randr-frog-layout-right-only () + "Use only the right screen on frog." + (interactive) + (set-randr-config `(("DisplayPort-0" ,(number-sequence 0 9)))) + (shell-command "xrandr --output DisplayPort-0 --off") + (shell-command "xrandr --output DisplayPort-1 --auto --primary")) + +(defun randr-frog-layout-both () + "Use the left and right screen on frog." + (interactive) + (set-randr-config `(("DisplayPort-0" 1 2 3 4 5) + ("DisplayPort-1" 6 7 8 9 0))) + + (shell-command "xrandr --output DisplayPort-0 --auto --primary --left-of DisplayPort-1") + (shell-command "xrandr --output DisplayPort-1 --auto --right-of DisplayPort-0 --rotate left")) + +(pcase (s-trim (shell-command-to-string "hostname")) + ("tverskoy" + (exwm-input-set-key (kbd "s-m s") #'randr-tverskoy-layout-single) + (exwm-input-set-key (kbd "s-m 2") #'randr-tverskoy-split-workspace)) + + ("frog" + (exwm-input-set-key (kbd "s-m b") #'randr-frog-layout-both) + (exwm-input-set-key (kbd "s-m r") #'randr-frog-layout-right-only))) + +;; Notmuch shortcuts as EXWM globals +;; (g m => gmail) +(exwm-input-set-key (kbd "s-g m") #'notmuch) +(exwm-input-set-key (kbd "s-g M") #'counsel-notmuch) + +(exwm-randr-enable) + +;; Let buffers move seamlessly between workspaces by making them +;; accessible in selectors on all frames. +(setq exwm-workspace-show-all-buffers t) +(setq exwm-layout-show-all-buffers t) + +(provide 'desktop) diff --git a/users/tazjin/emacs/config/eshell-setup.el b/users/tazjin/emacs/config/eshell-setup.el new file mode 100644 index 000000000000..0b23c5a2d1bc --- /dev/null +++ b/users/tazjin/emacs/config/eshell-setup.el @@ -0,0 +1,68 @@ +;; EShell configuration + +(require 'eshell) + +;; Generic settings +;; Hide banner message ... +(setq eshell-banner-message "") + +;; Prompt configuration +(defun clean-pwd (path) + "Turns a path of the form /foo/bar/baz into /f/b/baz + (inspired by fish shell)" + (let* ((hpath (replace-regexp-in-string home-dir + "~" + path)) + (current-dir (split-string hpath "/")) + (cdir (last current-dir)) + (head (butlast current-dir))) + (concat (mapconcat (lambda (s) + (if (string= "" s) nil + (substring s 0 1))) + head + "/") + (if head "/" nil) + (car cdir)))) + +(defun vcprompt (&optional args) + "Call the external vcprompt command with optional arguments. + VCPrompt" + (replace-regexp-in-string + "\n" "" + (shell-command-to-string (concat "vcprompt" args)))) + +(defmacro with-face (str &rest properties) + `(propertize ,str 'face (list ,@properties))) + +(defun prompt-f () + "EShell prompt displaying VC info and such" + (concat + (with-face (concat (clean-pwd (eshell/pwd)) " ") :foreground "#96a6c8") + (if (= 0 (user-uid)) + (with-face "#" :foreground "#f43841") + (with-face "$" :foreground "#73c936")) + (with-face " " :foreground "#95a99f"))) + + +(setq eshell-prompt-function 'prompt-f) +(setq eshell-highlight-prompt nil) +(setq eshell-prompt-regexp "^.+? \\((\\(git\\|svn\\|hg\\|darcs\\|cvs\\|bzr\\):.+?) \\)?[$#] ") + +;; Ignore version control folders in autocompletion +(setq eshell-cmpl-cycle-completions nil + eshell-save-history-on-exit t + eshell-cmpl-dir-ignore "\\`\\(\\.\\.?\\|CVS\\|\\.svn\\|\\.git\\)/\\'") + +;; Load some EShell extensions +(eval-after-load 'esh-opt + '(progn + (require 'em-term) + (require 'em-cmpl) + ;; More visual commands! + (add-to-list 'eshell-visual-commands "ssh") + (add-to-list 'eshell-visual-commands "tail") + (add-to-list 'eshell-visual-commands "sl"))) + +(setq eshell-directory-name "~/.config/eshell/") + +(provide 'eshell-setup) diff --git a/users/tazjin/emacs/config/functions.el b/users/tazjin/emacs/config/functions.el new file mode 100644 index 000000000000..5963d142c14b --- /dev/null +++ b/users/tazjin/emacs/config/functions.el @@ -0,0 +1,328 @@ +(require 'chart) +(require 'dash) +(require 'map) + +(defun load-file-if-exists (filename) + (if (file-exists-p filename) + (load filename))) + +(defun goto-line-with-feedback () + "Show line numbers temporarily, while prompting for the line number input" + (interactive) + (unwind-protect + (progn + (setq-local display-line-numbers t) + (let ((target (read-number "Goto line: "))) + (avy-push-mark) + (goto-line target))) + (setq-local display-line-numbers nil))) + +;; These come from the emacs starter kit + +(defun esk-add-watchwords () + (font-lock-add-keywords + nil '(("\\<\\(FIX\\(ME\\)?\\|TODO\\|DEBUG\\|HACK\\|REFACTOR\\|NOCOMMIT\\)" + 1 font-lock-warning-face t)))) + +(defun esk-sudo-edit (&optional arg) + (interactive "p") + (if (or arg (not buffer-file-name)) + (find-file (concat "/sudo:root@localhost:" (read-file-name "File: "))) + (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name)))) + +;; Open the NixOS man page +(defun nixos-man () + (interactive) + (man "configuration.nix")) + +;; Get the nix store path for a given derivation. +;; If the derivation has not been built before, this will trigger a build. +(defun nix-store-path (derivation) + (let ((expr (concat "with import <nixos> {}; " derivation))) + (s-chomp (shell-command-to-string (concat "nix-build -E '" expr "'"))))) + +(defun insert-nix-store-path () + (interactive) + (let ((derivation (read-string "Derivation name (in <nixos>): "))) + (insert (nix-store-path derivation)))) + +(defun toggle-force-newline () + "Buffer-local toggle for enforcing final newline on save." + (interactive) + (setq-local require-final-newline (not require-final-newline)) + (message "require-final-newline in buffer %s is now %s" + (buffer-name) + require-final-newline)) + +(defun list-external-commands () + "Creates a list of all external commands available on $PATH + while filtering NixOS wrappers." + (cl-loop + for dir in (split-string (getenv "PATH") path-separator) + when (and (file-exists-p dir) (file-accessible-directory-p dir)) + for lsdir = (cl-loop for i in (directory-files dir t) + for bn = (file-name-nondirectory i) + when (and (not (s-contains? "-wrapped" i)) + (not (member bn completions)) + (not (file-directory-p i)) + (file-executable-p i)) + collect bn) + append lsdir into completions + finally return (sort completions 'string-lessp))) + +(defvar external-command-flag-overrides + '(("google-chrome" . "--force-device-scale-factor=1.4")) + + "This setting lets me add additional flags to specific commands + that are run interactively via `run-external-command'.") + +(defun run-external-command--handler (cmd) + "Execute the specified command and notify the user when it + finishes." + (let* ((extra-flags (cdr (assoc cmd external-command-flag-overrides))) + (cmd (if extra-flags (s-join " " (list cmd extra-flags)) cmd))) + (message "Starting %s..." cmd) + (set-process-sentinel + (start-process-shell-command cmd nil cmd) + (lambda (process event) + (when (string= event "finished\n") + (message "%s process finished." process)))))) + +(defun run-external-command () + "Prompts the user with a list of all installed applications and + lets them select one to launch." + + (interactive) + (let ((external-commands-list (list-external-commands))) + (run-external-command--handler + (completing-read "Command: " external-commands-list + nil ;; predicate + t ;; require-match + nil ;; initial-input + ;; hist + 'external-commands-history)))) + +(defun password-store-lookup (&optional password-store-dir) + "Interactive password-store lookup function that actually uses +the GPG agent correctly." + + (interactive) + + (let* ((entry (completing-read "Copy password of entry: " + (password-store-list (or password-store-dir + (password-store-dir))) + nil ;; predicate + t ;; require-match + )) + (password (auth-source-pass-get 'secret entry))) + (password-store-clear) + (kill-new password) + (setq password-store-kill-ring-pointer kill-ring-yank-pointer) + (message "Copied %s to the kill ring. Will clear in %s seconds." + entry (password-store-timeout)) + (setq password-store-timeout-timer + (run-at-time (password-store-timeout) + nil 'password-store-clear)))) + +(defun browse-repositories () + "Select a git repository and open its associated magit buffer." + + (interactive) + (magit-status + (completing-read "Repository: " (magit-list-repos)))) + +(defun bottom-right-window-p () + "Determines whether the last (i.e. bottom-right) window of the + active frame is showing the buffer in which this function is + executed." + (let* ((frame (selected-frame)) + (right-windows (window-at-side-list frame 'right)) + (bottom-windows (window-at-side-list frame 'bottom)) + (last-window (car (seq-intersection right-windows bottom-windows)))) + (eq (current-buffer) (window-buffer last-window)))) + +(defhydra mc/mark-more-hydra (:color pink) + ("<up>" mmlte--up "Mark previous like this") + ("<down>" mc/mmlte--down "Mark next like this") + ("<left>" mc/mmlte--left (if (eq mc/mark-more-like-this-extended-direction 'up) + "Skip past the cursor furthest up" + "Remove the cursor furthest down")) + ("<right>" mc/mmlte--right (if (eq mc/mark-more-like-this-extended-direction 'up) + "Remove the cursor furthest up" + "Skip past the cursor furthest down")) + ("f" nil "Finish selecting")) + +;; Mute the message that mc/mmlte wants to print on its own +(advice-add 'mc/mmlte--message :around (lambda (&rest args) (ignore))) + +(defun mc/mark-dwim (arg) + "Select multiple things, but do what I mean." + + (interactive "p") + (if (not (region-active-p)) (mc/mark-next-lines arg) + (if (< 1 (count-lines (region-beginning) + (region-end))) + (mc/edit-lines arg) + ;; The following is almost identical to `mc/mark-more-like-this-extended', + ;; but uses a hydra (`mc/mark-more-hydra') instead of a transient key map. + (mc/mmlte--down) + (mc/mark-more-hydra/body)))) + +(defun memespace-region () + "Make a meme out of it." + + (interactive) + (let* ((start (region-beginning)) + (end (region-end)) + (memed + (message + (s-trim-right + (apply #'string + (-flatten + (nreverse + (-reduce-from (lambda (acc x) + (cons (cons x (-repeat (+ 1 (length acc)) 32)) acc)) + '() + (string-to-list (buffer-substring-no-properties start end)))))))))) + + (save-excursion (delete-region start end) + (goto-char start) + (insert memed)))) + +(defun insert-todo-comment (prefix todo) + "Insert a comment at point with something for me to do." + + (interactive "P\nsWhat needs doing? ") + (save-excursion + (move-end-of-line nil) + (insert (format " %s TODO(%s): %s" + (s-trim-right comment-start) + (if prefix (read-string "Who needs to do this? ") + (getenv "USER")) + todo)))) + +;; Custom text scale adjustment functions that operate on the entire instance +(defun modify-text-scale (factor) + (set-face-attribute 'default nil + :height (+ (* factor 5) (face-attribute 'default :height)))) + +(defun increase-default-text-scale (prefix) + "Increase default text scale in all Emacs frames, or just the + current frame if PREFIX is set." + + (interactive "P") + (if prefix (text-scale-increase 1) + (modify-text-scale 1))) + +(defun decrease-default-text-scale (prefix) + "Increase default text scale in all Emacs frames, or just the + current frame if PREFIX is set." + + (interactive "P") + (if prefix (text-scale-decrease 1) + (modify-text-scale -1))) + +(defun set-default-text-scale (prefix &optional to) + "Set the default text scale to the specified value, or the + default. Restores current frame's text scale only, if PREFIX is + set." + + (interactive "P") + (if prefix (text-scale-adjust 0) + (set-face-attribute 'default nil :height (or to 120)))) + +(defun scrot-select () + "Take a screenshot based on a mouse-selection and save it to + ~/screenshots." + (interactive) + (shell-command "scrot '$a_%Y-%m-%d_%s.png' -s -e 'mv $f ~/screenshots/'")) + +(defun graph-unread-mails () + "Create a bar chart of unread mails based on notmuch tags. + Certain tags are excluded from the overview." + + (interactive) + (let ((tag-counts + (-keep (-lambda ((name . search)) + (let ((count + (string-to-number + (s-trim + (notmuch-command-to-string "count" search "and" "tag:unread"))))) + (when (>= count 1) (cons name count)))) + (notmuch-hello-generate-tag-alist '("unread" "signed" "attachment" "important"))))) + + (chart-bar-quickie + (if (< (length tag-counts) 6) + 'vertical 'horizontal) + "Unread emails" + (-map #'car tag-counts) "Tag:" + (-map #'cdr tag-counts) "Count:"))) + +(defun notmuch-show-open-or-close-subthread (&optional prefix) + "Open or close the subthread from (and including) the message at point." + (interactive "P") + (save-excursion + (let ((current-depth (map-elt (notmuch-show-get-message-properties) :depth 0))) + (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) prefix) + until (or (not (notmuch-show-goto-message-next)) + (= (map-elt (notmuch-show-get-message-properties) :depth) current-depth))))) + (force-window-update)) + +(defun vterm-send-ctrl-x () + "Sends `C-x' to the libvterm." + (interactive) + (vterm-send-key "x" nil nil t)) + +(defun find-depot-project (dir) + "Function used in the `project-find-functions' hook list to + determine the current project root of a depot project." + (when (s-starts-with? "/depot" dir) + (if (f-exists-p (f-join dir "default.nix")) + (cons 'transient dir) + (find-depot-project (f-parent dir))))) + +(add-to-list 'project-find-functions #'find-depot-project) + +(defun magit-find-file-worktree () + (interactive) + "Find a file in the current (ma)git worktree." + (magit-find-file--internal "{worktree}" + (magit-read-file-from-rev "HEAD" "Find file") + #'pop-to-buffer-same-window)) + +(defun songwhip--handle-result (status &optional cbargs) + ;; TODO(tazjin): Inspect status, which looks different in practice + ;; than the manual claims. + (if-let* ((response (json-parse-string + (buffer-substring url-http-end-of-headers (point-max)))) + (sw-path (ht-get* response "data" "path")) + (link (format "https://songwhip.com/%s" sw-path)) + (select-enable-clipboard t)) + (progn + (kill-new link) + (message "Copied Songwhip link (%s)" link)) + (warn "Something went wrong while retrieving Songwhip link!") + ;; For debug purposes, the buffer is persisted in this case. + (setq songwhip--debug-buffer (current-buffer)))) + +(defun songwhip-lookup-url (url) + "Look up URL on Songwhip and copy the resulting link to the clipboard." + (interactive "sEnter source URL: ") + (let ((songwhip-url "https://songwhip.com/api/") + (url-request-method "POST") + (url-request-extra-headers '(("Content-Type" . "application/json"))) + (url-request-data + (json-serialize `((country . "GB") + (url . ,url))))) + (url-retrieve "https://songwhip.com/api/" #'songwhip--handle-result nil t t) + (message "Requesting Songwhip URL ... please hold the line."))) + +(defun rg-in-project (&optional prefix) + "Interactively call ripgrep in the current project, or fall + back to ripgrep default behaviour if prefix is set." + (interactive "P") + (counsel-rg nil (unless prefix + (if-let ((pr (project-current))) + (project-root pr))))) + +(provide 'functions) diff --git a/users/tazjin/emacs/config/init.el b/users/tazjin/emacs/config/init.el new file mode 100644 index 000000000000..7d45dbb4eec9 --- /dev/null +++ b/users/tazjin/emacs/config/init.el @@ -0,0 +1,311 @@ +;;; init.el --- Package bootstrapping. -*- lexical-binding: t; -*- + +;; Disable annoying warnings from native compilation. +(setq native-comp-async-report-warnings-errors nil + warning-suppress-log-types '((comp))) + +;; Packages are installed via Nix configuration, this file only +;; initialises the newly loaded packages. + +(require 'use-package) +(require 'seq) + +;; TODO(tazjin): Figure out what's up with vc. +;; +;; Leaving vc enabled breaks all find-file operations with messages +;; about .git folders being absent, but in random places. +(require 'vc) +(setq vc-handled-backends nil) + +(package-initialize) + +;; Initialise all packages installed via Nix. +;; +;; TODO: Generate this section in Nix for all packages that do not +;; require special configuration. + +;; +;; Packages providing generic functionality. +;; + +(use-package ace-window + :bind (("C-x o" . ace-window)) + :config + (setq aw-keys '(?f ?j ?d ?k ?s ?l ?a) + aw-scope 'frame)) + +(use-package auth-source-pass :config (auth-source-pass-enable)) + +(use-package avy + :bind (("M-j" . avy-goto-char) + ("M-p" . avy-pop-mark) + ("M-g g" . avy-goto-line))) + +(use-package browse-kill-ring) + +(use-package company + :hook ((prog-mode . company-mode)) + :config (setq company-tooltip-align-annotations t)) + +(use-package counsel + :after (ivy) + :config (counsel-mode 1)) + +(use-package dash) +(use-package gruber-darker-theme) + +(use-package eglot + :custom + (eglot-autoshutdown t) + (eglot-send-changes-idle-time 0.3)) + +(use-package elfeed + :config + (setq elfeed-feeds + '("https://lobste.rs/rss" + "https://www.anti-spiegel.ru/feed/" + "https://www.reddit.com/r/lockdownskepticism/.rss" + "https://www.reddit.com/r/rust/.rss" + "https://news.ycombinator.com/rss" + ("https://xkcd.com/atom.xml" media) + + ;; vlogcreations + ("https://www.youtube.com/feeds/videos.xml?channel_id=UCR0VLWitB2xM4q7tjkoJUPw" media) + ))) + +(use-package ht) + +(use-package hydra) +(use-package idle-highlight-mode :hook ((prog-mode . idle-highlight-mode))) + +(use-package ivy + :config + (ivy-mode 1) + (setq enable-recursive-minibuffers t) + (setq ivy-use-virtual-buffers t)) + +(use-package ivy-prescient + :after (ivy prescient) + :config + (ivy-prescient-mode) + ;; Fixes an issue with how regexes are passed to ripgrep from counsel, + ;; see raxod502/prescient.el#43 + (setf (alist-get 'counsel-rg ivy-re-builders-alist) #'ivy--regex-plus)) + +(use-package multiple-cursors) + +(use-package notmuch + :custom + (notmuch-search-oldest-first nil) + (notmuch-show-all-tags-list t) + (notmuch-hello-tag-list-make-query "tag:unread")) + +(use-package paredit :hook ((lisp-mode . paredit-mode) + (emacs-lisp-mode . paredit-mode))) + +(use-package pinentry + :config + (setq epa-pinentry-mode 'loopback) + (pinentry-start)) + +(use-package prescient + :after (ivy counsel) + :config (prescient-persist-mode)) + +(use-package rainbow-delimiters + :hook (prog-mode . rainbow-delimiters-mode) + :custom-face + (rainbow-delimiters-depth-1-face ((t (:foreground "#2aa198")))) + (rainbow-delimiters-depth-2-face ((t (:foreground "#b58900")))) + (rainbow-delimiters-depth-3-face ((t (:foreground "#268bd2")))) + (rainbow-delimiters-depth-4-face ((t (:foreground "#dc322f")))) + (rainbow-delimiters-depth-5-face ((t (:foreground "#859900")))) + (rainbow-delimiters-depth-6-face ((t (:foreground "#268bd2")))) + (rainbow-delimiters-depth-7-face ((t (:foreground "#cb4b16")))) + (rainbow-delimiters-depth-8-face ((t (:foreground "#d33682")))) + (rainbow-delimiters-depth-9-face ((t (:foreground "#839496"))))) + +(use-package rainbow-mode) +(use-package s) +(use-package string-edit) + +(use-package swiper + :after (counsel ivy) + :bind (("C-s" . swiper))) + +(use-package telephone-line) ;; configuration happens outside of use-package +(use-package term-switcher) +(use-package undo-tree :config (global-undo-tree-mode)) +(use-package uuidgen) +(use-package which-key :config (which-key-mode t)) + +;; +;; Applications in emacs +;; + +(use-package magit + :bind ("C-c g" . magit-status) + :config (setq magit-repository-directories '(("/home/tazjin/projects" . 2) + ("/home/tazjin" . 1)))) + +(use-package password-store) +(use-package restclient) + +(use-package vterm + :config (progn + (setq vterm-shell "fish") + (setq vterm-exit-functions + (lambda (&rest _) (kill-buffer (current-buffer)))) + (setq vterm-kill-buffer-on-exit t)) + :custom-face + (term-color-black ((t (:background "#282828" :foreground "#282828")))) + (term-color-blue ((t (:background "#96a6c8" :foreground "#96a6c8")))) + (term-color-cyan ((t (:background "#1fad83" :foreground "#1fad83")))) + (term-color-green ((t (:background "#73c936" :foreground "#73c936")))) + (term-color-magenta ((t (:background "#9e95c7" :foreground "#9e95c7")))) + (term-color-red ((t (:background "#f43841" :foreground "#f43841")))) + (term-color-white ((t (:background "#f5f5f5" :foreground "#f5f5f5")))) + (term-color-yellow ((t (:background "#ffdd33" :foreground "#ffdd33"))))) + +;; vterm removed the ability to set a custom title generator function +;; via the public API, so this overrides its private title generation +;; function instead +(defun vterm--set-title (title) + (rename-buffer + (generate-new-buffer-name + (format "vterm<%s>" + (s-trim-left + (s-chop-prefix "fish" title)))))) + +;; +;; Packages providing language-specific functionality +;; + +(use-package cargo + :hook ((rust-mode . cargo-minor-mode) + (cargo-process-mode . visual-line-mode)) + :bind (:map cargo-mode-map ("C-c C-c C-l" . ignore))) + +(use-package dockerfile-mode) + +(use-package erlang + :hook ((erlang-mode . (lambda () + ;; Don't indent after '>' while I'm writing + (local-set-key ">" 'self-insert-command))))) + +(use-package f) + +(use-package go-mode + :bind (:map go-mode-map ("C-c C-r" . recompile)) + :hook ((go-mode . (lambda () + (setq tab-width 2) + (setq-local compile-command + (concat "go build " buffer-file-name)))))) + +(use-package haskell-mode) + +(use-package ielm + :hook ((inferior-emacs-lisp-mode . (lambda () + (paredit-mode) + (rainbow-delimiters-mode-enable) + (company-mode))))) + +(use-package jq-mode + :config (add-to-list 'auto-mode-alist '("\\.jq\\'" . jq-mode))) + +(use-package kotlin-mode + :hook ((kotlin-mode . (lambda () + (setq indent-line-function #'indent-relative))))) + +(use-package lsp-mode) + +(use-package markdown-mode + :config + (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) + (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))) + +(use-package markdown-toc) + +(use-package nix-mode + :hook ((nix-mode . (lambda () + (setq indent-line-function #'nix-indent-line))))) + +(use-package nix-util) +(use-package nginx-mode) +(use-package rust-mode) + +(use-package sly + :hook ((sly-mrepl-mode . (lambda () + (paredit-mode) + (rainbow-delimiters-mode-enable) + (company-mode)))) + :config + (setq common-lisp-hyperspec-root "file:///home/tazjin/docs/lisp/")) + +(use-package telega + :bind (:map global-map ("s-t" . telega)) + :config + (telega-mode-line-mode 1) + (add-hook 'telega-msg-ignore-predicates 'telega-msg-from-blocked-sender-p)) + +(use-package terraform-mode) +(use-package toml-mode) + +(use-package tvl) + +(use-package web-mode) +(use-package yaml-mode) + +(use-package passively + :custom + (passively-store-state "/persist/tazjin/known-russian-words.el")) + +;; Initialise midnight.el, which by default automatically cleans up +;; unused buffers at midnight. +(require 'midnight) + +(defgroup tazjin nil + "Settings related to my configuration") + +(defcustom depot-path "/depot" + "Local path to the depot checkout" + :group 'tazjin) + +;; Configuration changes in `customize` can not actually be persisted +;; to the customise file that Emacs is currently using (since it comes +;; from the Nix store). +;; +;; The way this will work for now is that Emacs will *write* +;; configuration to the file tracked in my repository, while not +;; actually *reading* it from there (unless Emacs is rebuilt). +(setq custom-file (expand-file-name "~/depot/tools/emacs/config/custom.el")) +(load-library "custom") + +(defvar home-dir (expand-file-name "~")) + +;; Seed RNG +(random t) + +;; Load all other Emacs configuration. These configurations are +;; added to `load-path' by Nix. +(mapc 'require '(desktop + mail-setup + look-and-feel + functions + settings + modes + bindings + eshell-setup)) +(telephone-line-setup) +(ace-window-display-mode) + +;; If a local configuration library exists, it should be loaded. +;; +;; This can be provided by calling my Emacs derivation with +;; `withLocalConfig'. +(if-let (local-file (locate-library "local")) + (load local-file)) + +(require 'dottime) + +(provide 'init) diff --git a/users/tazjin/emacs/config/look-and-feel.el b/users/tazjin/emacs/config/look-and-feel.el new file mode 100644 index 000000000000..8cca6e1bf08e --- /dev/null +++ b/users/tazjin/emacs/config/look-and-feel.el @@ -0,0 +1,122 @@ +;;; -*- lexical-binding: t; -*- + +;; Hide those ugly tool bars: +(tool-bar-mode 0) +(scroll-bar-mode 0) +(menu-bar-mode 0) +(add-hook 'after-make-frame-functions + (lambda (frame) (scroll-bar-mode 0))) + +;; Don't do any annoying things: +(setq ring-bell-function 'ignore) +(setq initial-scratch-message "") + +;; Remember layout changes +(winner-mode 1) + +;; Usually emacs will run as a proper GUI application, in which case a few +;; extra settings are nice-to-have: +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b"))) + (mouse-wheel-mode t) + (blink-cursor-mode -1)) + +;; Configure Emacs fonts. +(let ((font (if (equal "frog" (s-trim (shell-command-to-string "hostname"))) + ;; For unclear reasons, frog refuses to render the + ;; regular font weight - everything ends up bold, + ;; which makes it hard to distinguish e.g. read/unread + ;; emails. + ;; + ;; Semi-bold looks a little different than on vauxhall + ;; and other machines, but it's alright. + (format "JetBrains Mono Semi Light-%d" 12) + (format "JetBrains Mono-%d" 12)))) + (setq default-frame-alist `((font . ,font))) + (set-frame-font font t t)) + +;; Configure telephone-line +(defun telephone-misc-if-last-window () + "Renders the mode-line-misc-info string for display in the + mode-line if the currently active window is the last one in the + frame. + + The idea is to not display information like the current time, + load, battery levels on all buffers." + + (when (bottom-right-window-p) + (telephone-line-raw mode-line-misc-info t))) + +(defun telephone-line-setup () + (telephone-line-defsegment telephone-line-last-window-segment () + (telephone-misc-if-last-window)) + + ;; Display the current EXWM workspace index in the mode-line + (telephone-line-defsegment telephone-line-exwm-workspace-index () + (when (bottom-right-window-p) + (format "[%s]" exwm-workspace-current-index))) + + ;; Define a highlight font for ~ important ~ information in the last + ;; window. + (defface special-highlight '((t (:foreground "white" :background "#5f627f"))) "") + (add-to-list 'telephone-line-faces + '(highlight . (special-highlight . special-highlight))) + + (setq telephone-line-lhs + '((nil . (telephone-line-position-segment)) + (accent . (telephone-line-buffer-segment)))) + + (setq telephone-line-rhs + '((accent . (telephone-line-major-mode-segment)) + (nil . (telephone-line-last-window-segment + telephone-line-exwm-workspace-index)) + + ;; TODO(tazjin): lets not do this particular thing while I + ;; don't actually run notmuch, there are too many things + ;; that have a dependency on the modeline drawing correctly + ;; (including randr operations!) + ;; + ;; (highlight . (telephone-line-notmuch-counts)) + )) + + (setq telephone-line-primary-left-separator 'telephone-line-tan-left + telephone-line-primary-right-separator 'telephone-line-tan-right + telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left + telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) + + (telephone-line-mode 1)) + +;; Auto refresh buffers +(global-auto-revert-mode 1) + +;; Use clipboard properly +(setq select-enable-clipboard t) + +;; Show in-progress chords in minibuffer +(setq echo-keystrokes 0.1) + +;; Show column numbers in all buffers +(column-number-mode t) + +(defalias 'yes-or-no-p 'y-or-n-p) +(defalias 'auto-tail-revert-mode 'tail-mode) + +;; Style line numbers (shown with M-g g) +(setq linum-format + (lambda (line) + (propertize + (format (concat " %" + (number-to-string + (length (number-to-string + (line-number-at-pos (point-max))))) + "d ") + line) + 'face 'linum))) + +;; Display tabs as 2 spaces +(setq tab-width 2) + +;; Don't wrap around when moving between buffers +(setq windmove-wrap-around nil) + +(provide 'look-and-feel) diff --git a/users/tazjin/emacs/config/mail-setup.el b/users/tazjin/emacs/config/mail-setup.el new file mode 100644 index 000000000000..7fbece1b102a --- /dev/null +++ b/users/tazjin/emacs/config/mail-setup.el @@ -0,0 +1,85 @@ +(require 'notmuch) +(require 'counsel-notmuch) + +;; (global-set-key (kbd "C-c m") 'notmuch-hello) +;; (global-set-key (kbd "C-c C-m") 'counsel-notmuch) +;; (global-set-key (kbd "C-c C-e n") 'notmuch-mua-new-mail) + +(setq notmuch-cache-dir (format "%s/.cache/notmuch" (getenv "HOME"))) +(make-directory notmuch-cache-dir t) + +;; Cache addresses for completion: +(setq notmuch-address-save-filename (concat notmuch-cache-dir "/addresses")) + +;; Don't spam my home folder with drafts: +(setq notmuch-draft-folder "drafts") ;; relative to notmuch database + +;; Mark things as read when archiving them: +(setq notmuch-archive-tags '("-inbox" "-unread" "+archive")) + +;; Show me saved searches that I care about: +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :count-query "tag:inbox AND tag:unread" :key "i") + (:name "sent" :query "tag:sent" :key "t") + (:name "drafts" :query "tag:draft"))) +(setq notmuch-show-empty-saved-searches t) + +;; Mail sending configuration +(setq sendmail-program "gmi") ;; lieer binary supports sendmail emulation +(setq message-sendmail-extra-arguments + '("send" "--quiet" "-t" "-C" "~/mail/account.tazjin")) +(setq send-mail-function 'sendmail-send-it) +(setq notmuch-mua-user-agent-function + (lambda () (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) +(setq mail-host-address (system-name)) +(setq notmuch-mua-cite-function #'message-cite-original-without-signature) +(setq notmuch-fcc-dirs nil) ;; Gmail does this server-side +(setq message-signature nil) ;; Insert message signature manually with C-c C-w + +;; Close mail buffers after sending mail +(setq message-kill-buffer-on-exit t) + +;; Ensure sender is correctly passed to msmtp +(setq mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header) + +;; Store sent mail in the correct folder per account +(setq notmuch-maildir-use-notmuch-insert nil) + +;; I don't use drafts but I instinctively hit C-x C-s constantly, lets +;; handle that gracefully. +(define-key notmuch-message-mode-map (kbd "C-x C-s") #'ignore) + +;; Define a telephone-line segment for displaying the count of unread, +;; important mails in the last window's mode-line: +(defvar *last-notmuch-count-redraw* 0) +(defvar *current-notmuch-count* nil) + +(defun update-display-notmuch-counts () + "Update and render the current state of the notmuch unread + count for display in the mode-line. + + The offlineimap-timer runs every 2 minutes, so it does not make + sense to refresh this much more often than that." + + (when (> (- (float-time) *last-notmuch-count-redraw*) 30) + (setq *last-notmuch-count-redraw* (float-time)) + (let* ((inbox-unread (notmuch-saved-search-count "tag:inbox and tag:unread")) + (notmuch-count (format "I: %s; D: %s" inbox-unread))) + (setq *current-notmuch-count* notmuch-count))) + + (when (and (bottom-right-window-p) + ;; Only render if the initial update is done and there + ;; are unread mails: + *current-notmuch-count* + (not (equal *current-notmuch-count* "I: 0; D: 0"))) + *current-notmuch-count*)) + +(telephone-line-defsegment telephone-line-notmuch-counts () + "This segment displays the count of unread notmuch messages in + the last window's mode-line (if unread messages are present)." + + (update-display-notmuch-counts)) + +(provide 'mail-setup) diff --git a/users/tazjin/emacs/config/modes.el b/users/tazjin/emacs/config/modes.el new file mode 100644 index 000000000000..69fb523d0d91 --- /dev/null +++ b/users/tazjin/emacs/config/modes.el @@ -0,0 +1,37 @@ +;; Initializes modes I use. + +(add-hook 'prog-mode-hook 'esk-add-watchwords) +(add-hook 'prog-mode-hook 'hl-line-mode) + +;; Use auto-complete as completion at point +(defun set-auto-complete-as-completion-at-point-function () + (setq completion-at-point-functions '(auto-complete))) + +(add-hook 'auto-complete-mode-hook + 'set-auto-complete-as-completion-at-point-function) + +;; Enable rainbow-delimiters for all things programming +(add-hook 'prog-mode-hook 'rainbow-delimiters-mode) + +;; Enable Paredit & Company in Emacs Lisp mode +(add-hook 'emacs-lisp-mode-hook 'company-mode) + +;; Always highlight matching brackets +(show-paren-mode 1) + +;; Always auto-close parantheses and other pairs +(electric-pair-mode) + +;; Keep track of recent files +(recentf-mode) + +;; Easily navigate sillycased words +(global-subword-mode 1) + +;; Transparently open compressed files +(auto-compression-mode t) + +;; Configure go-mode for Go2 Alpha +(add-to-list 'auto-mode-alist '("\\.go2$" . go-mode)) + +(provide 'modes) diff --git a/users/tazjin/emacs/config/settings.el b/users/tazjin/emacs/config/settings.el new file mode 100644 index 000000000000..8b15b6cda183 --- /dev/null +++ b/users/tazjin/emacs/config/settings.el @@ -0,0 +1,48 @@ +(require 'uniquify) + +;; We don't live in the 80s, but we're also not a shitty web app. +(setq gc-cons-threshold 20000000) + +(setq uniquify-buffer-name-style 'forward) + +; Fix some defaults +(setq visible-bell nil + inhibit-startup-message t + color-theme-is-global t + sentence-end-double-space nil + shift-select-mode nil + uniquify-buffer-name-style 'forward + whitespace-style '(face trailing lines-tail tabs) + whitespace-line-column 80 + default-directory "~" + fill-column 80 + ediff-split-window-function 'split-window-horizontally + initial-major-mode 'emacs-lisp-mode) + +(add-to-list 'safe-local-variable-values '(lexical-binding . t)) +(add-to-list 'safe-local-variable-values '(whitespace-line-column . 80)) + +(set-default 'indent-tabs-mode nil) + +;; UTF-8 please +(setq locale-coding-system 'utf-8) ; pretty +(set-terminal-coding-system 'utf-8) ; pretty +(set-keyboard-coding-system 'utf-8) ; pretty +(set-selection-coding-system 'utf-8) ; please +(prefer-coding-system 'utf-8) ; with sugar on top + +;; Make emacs behave sanely (overwrite selected text) +(delete-selection-mode 1) + +;; Keep your temporary files in tmp, emacs! +(setq auto-save-file-name-transforms + `((".*" ,temporary-file-directory t))) +(setq backup-directory-alist + `((".*" . ,temporary-file-directory))) + +(remove-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function) + +;; Show time in 24h format +(setq display-time-24hr-format t) + +(provide 'settings) diff --git a/users/tazjin/emacs/default.nix b/users/tazjin/emacs/default.nix new file mode 100644 index 000000000000..381f246aa925 --- /dev/null +++ b/users/tazjin/emacs/default.nix @@ -0,0 +1,168 @@ +# This file builds an Emacs pre-configured with the packages I need +# and my personal Emacs configuration. +# +# On NixOS machines, this Emacs currently does not support +# Imagemagick, see https://github.com/NixOS/nixpkgs/issues/70631. +# +# Forcing Emacs to link against Imagemagick currently causes libvterm +# to segfault, which is a lot less desirable than not having telega +# render images correctly. +{ lib, pkgs, ... }: + +pkgs.makeOverridable({ emacs ? pkgs.emacsGcc }: +let + emacsWithPackages = (pkgs.emacsPackagesGen emacs).emacsWithPackages; + + # If switching telega versions, use this variable because it will + # keep the version check, binary path and so on in sync. + currentTelega = epkgs: epkgs.melpaPackages.telega; + + # $PATH for binaries that need to be available to Emacs + emacsBinPath = lib.makeBinPath [ (currentTelega pkgs.emacsPackages) ]; + + identity = x: x; + + tazjinsEmacs = pkgfun: (emacsWithPackages(epkgs: pkgfun(with epkgs; [ + ace-link + ace-window + avy + bazel + browse-kill-ring + cargo + clojure-mode + cmake-mode + company + counsel + counsel-notmuch + d-mode + direnv + dockerfile-mode + eglot + elfeed + elixir-mode + elm-mode + erlang + exwm + flymake + go-mode + google-c-style + gruber-darker-theme + haskell-mode + ht + hydra + idle-highlight-mode + ivy + ivy-prescient + jq-mode + kotlin-mode + lsp-mode + magit + markdown-toc + meson-mode + multi-term + multiple-cursors + nginx-mode + nix-mode + notmuch + paredit + password-store + pinentry + polymode + prescient + protobuf-mode + rainbow-delimiters + rainbow-mode + refine + request + restclient + rust-mode + sly + string-edit + swiper + telephone-line + terraform-mode + toml-mode + transient + undo-tree + use-package + uuidgen + vterm + web-mode + websocket + which-key + xelb + yaml-mode + yasnippet + + # Wonky stuff + (currentTelega epkgs) + + # Custom depot packages (either ours, or overridden ones) + tvlPackages.dottime + tvlPackages.nix-util + tvlPackages.passively + tvlPackages.rcirc + tvlPackages.term-switcher + tvlPackages.tvl + ]))); + + # Tired of telega.el runtime breakages through tdlib + # incompatibility. Target to make that a build failure instead. + tdlibCheck = + let + tgEmacs = emacsWithPackages(epkgs: [ (currentTelega epkgs) ]); + verifyTdlibVersion = builtins.toFile "verify-tdlib-version.el" '' + (require 'telega) + (defvar tdlib-version "${pkgs.tdlib.version}") + (when (or (version< tdlib-version + telega-tdlib-min-version) + (and telega-tdlib-max-version + (version< telega-tdlib-max-version + tdlib-version))) + (message "Found TDLib version %s, but require %s to %s" + tdlib-version telega-tdlib-min-version telega-tdlib-max-version) + (kill-emacs 1)) + ''; + in pkgs.runCommandNoCC "tdlibCheck" {} '' + export PATH="${emacsBinPath}:$PATH" + ${tgEmacs}/bin/emacs --script ${verifyTdlibVersion} && touch $out + ''; +in lib.fix(self: l: f: pkgs.writeShellScriptBin "tazjins-emacs" '' + export PATH="${emacsBinPath}:$PATH" + exec ${tazjinsEmacs f}/bin/emacs \ + --debug-init \ + --no-site-file \ + --no-site-lisp \ + --no-init-file \ + --directory ${./config} ${if l != null then "--directory ${l}" else ""} \ + --eval "(require 'init)" $@ + '' // { + # Call overrideEmacs with a function (pkgs -> pkgs) to modify the + # packages that should be included in this Emacs distribution. + overrideEmacs = f': self l f'; + + # Call withLocalConfig with the path to a *folder* containing a + # `local.el` which provides local system configuration. + withLocalConfig = confDir: self confDir f; + + # Build a derivation that uses the specified local Emacs (i.e. + # built outside of Nix) instead + withLocalEmacs = emacsBin: pkgs.writeShellScriptBin "tazjins-emacs" '' + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${(tazjinsEmacs f).deps}/share/emacs/site-lisp:" + exec ${emacsBin} \ + --debug-init \ + --no-site-file \ + --no-site-lisp \ + --no-init-file \ + --directory ${./config} \ + ${if l != null then "--directory ${l}" else ""} \ + --eval "(require 'init)" $@ + ''; + + # Expose telega/tdlib version check as a target that is built in + # CI. + inherit tdlibCheck; + meta.targets = [ "tdlibCheck" ]; + }) null identity +) {} diff --git a/users/tazjin/finito/.gitignore b/users/tazjin/finito/.gitignore new file mode 100644 index 000000000000..548206b0b297 --- /dev/null +++ b/users/tazjin/finito/.gitignore @@ -0,0 +1,3 @@ +.envrc +/target/ +**/*.rs.bk diff --git a/users/tazjin/finito/Cargo.lock b/users/tazjin/finito/Cargo.lock new file mode 100644 index 000000000000..7427a6b11c42 --- /dev/null +++ b/users/tazjin/finito/Cargo.lock @@ -0,0 +1,773 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler32" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" +dependencies = [ + "byteorder", + "safemem", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" +dependencies = [ + "arrayref", + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crypto-mac" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0999b4ff4d3446d4ddb19a63e9e00c1876e75cd7000d20e57a693b4b3f08d958" +dependencies = [ + "constant_time_eq", + "generic-array", +] + +[[package]] +name = "digest" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +dependencies = [ + "generic-array", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7217124812dc5672b7476d0c2d20cfe9f7c0f1ba0904b674a9762a0212f72e" + +[[package]] +name = "finito" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "finito-door" +version = "0.1.0" +dependencies = [ + "failure", + "finito", + "serde", + "serde_derive", +] + +[[package]] +name = "finito-postgres" +version = "0.1.0" +dependencies = [ + "chrono", + "finito", + "finito-door", + "postgres", + "postgres-derive", + "r2d2_postgres", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +dependencies = [ + "typenum", +] + +[[package]] +name = "gimli" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" + +[[package]] +name = "hex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" + +[[package]] +name = "hmac" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f3bdb08579d99d7dc761c0e266f13b5f2ab8c8c703b9fc9ef333cd8f48f55e" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "md5" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", +] + +[[package]] +name = "postgres" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dde90ef51af573580c035857badbece2aa5cde3de1dfb3c932969ca92a6c5" +dependencies = [ + "bytes", + "fallible-iterator", + "log", + "postgres-protocol", + "postgres-shared", + "socket2", +] + +[[package]] +name = "postgres-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44ef42ae50f1547dde36aa78d5e44189cbf21f4e77ce6ddc2bbaa068337fc221" +dependencies = [ + "quote 0.5.2", + "syn 0.13.11", +] + +[[package]] +name = "postgres-protocol" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2487e66455bf88a1b247bf08a3ce7fe5197ac6d67228d920b0ee6a0e97fd7312" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "generic-array", + "hmac", + "md5", + "memchr", + "rand 0.3.23", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-shared" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffac35b3e0029b404c24a3b82149b4e904f293e8ca4a327eefa24d3ca50df36f" +dependencies = [ + "chrono", + "fallible-iterator", + "hex", + "phf", + "postgres-protocol", + "serde_json", + "uuid", +] + +[[package]] +name = "proc-macro2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid 0.2.1", +] + +[[package]] +name = "quote" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" +dependencies = [ + "proc-macro2 0.3.8", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2 1.0.18", +] + +[[package]] +name = "r2d2" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_postgres" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c7fe9c0c3d2c298cf262bc3ce4b89cdf0eab620fd9fe759f65b34a1a00fb93" +dependencies = [ + "postgres", + "postgres-shared", + "r2d2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safemem" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" + +[[package]] +name = "scheduled-thread-pool" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" +dependencies = [ + "block-buffer", + "byte-tools", + "digest", + "fake-simd", +] + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "syn" +version = "0.13.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" +dependencies = [ + "proc-macro2 0.3.8", + "quote 0.5.2", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "unicode-xid 0.2.1", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "unicode-xid 0.2.1", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "uuid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" +dependencies = [ + "rand 0.3.23", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/users/tazjin/finito/Cargo.toml b/users/tazjin/finito/Cargo.toml new file mode 100644 index 000000000000..310133abeebb --- /dev/null +++ b/users/tazjin/finito/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "finito-core", + "finito-door", + "finito-postgres" +] diff --git a/users/tazjin/finito/README.md b/users/tazjin/finito/README.md new file mode 100644 index 000000000000..5acd67d3bea7 --- /dev/null +++ b/users/tazjin/finito/README.md @@ -0,0 +1,27 @@ +Finito +====== + +This is a Rust port of the Haskell state-machine library Finito. It is +slightly less featureful because it loses the ability to ensure that +side-effects are contained and because of a slight reduction in +expressivity, which makes it a bit more restrictive. + +However, it still implements the FSM model well enough. + +# Components + +Finito is split up into multiple independent components (note: not all +of these exist yet), separating functionality related to FSM +persistence from other things. + +* `finito`: Core abstraction implemented by Finito +* `finito-door`: Example implementation of a simple, lockable door +* `finito-postgres`: Persistent state-machines using Postgres + +**Note**: The `finito` core library does not contain any tests. Its +coverage is instead provided by the `finito-door` library, which +actually implements an example FSM. + +These are split out because the documentation for `finito-door` is +interesting regardless and because other Finito packages also need an +example implementation. diff --git a/users/tazjin/finito/default.nix b/users/tazjin/finito/default.nix new file mode 100644 index 000000000000..e50ac32be452 --- /dev/null +++ b/users/tazjin/finito/default.nix @@ -0,0 +1,5 @@ +{ depot, ... }: + +depot.third_party.naersk.buildPackage { + src = ./.; +} diff --git a/users/tazjin/finito/finito-core/Cargo.toml b/users/tazjin/finito/finito-core/Cargo.toml new file mode 100644 index 000000000000..1d7bdb8b01fe --- /dev/null +++ b/users/tazjin/finito/finito-core/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "finito" +version = "0.1.0" +authors = ["Vincent Ambo <mail@tazj.in>"] + +[dependencies] +serde = "1.0" diff --git a/users/tazjin/finito/finito-core/src/lib.rs b/users/tazjin/finito/finito-core/src/lib.rs new file mode 100644 index 000000000000..517bfad2bc74 --- /dev/null +++ b/users/tazjin/finito/finito-core/src/lib.rs @@ -0,0 +1,243 @@ +//! Finito's core finite-state machine abstraction. +//! +//! # What & why? +//! +//! Most processes that occur in software applications can be modeled +//! as finite-state machines (FSMs), however the actual states, the +//! transitions between them and the model's interaction with the +//! external world is often implicit. +//! +//! Making the states of a process explicit using a simple language +//! that works for both software developers and other people who may +//! have opinions on processes makes it easier to synchronise thoughts, +//! extend software and keep a good level of control over what is going +//! on. +//! +//! This library aims to provide functionality for implementing +//! finite-state machines in a way that balances expressivity and +//! safety. +//! +//! Finito does not aim to prevent every possible incorrect +//! transition, but aims for somewhere "safe-enough" (please don't +//! lynch me) that is still easily understood. +//! +//! # Conceptual overview +//! +//! The core idea behind Finito can be expressed in a single line and +//! will potentially look familiar if you have used Erlang in a +//! previous life. The syntax used here is the type-signature notation +//! of Haskell. +//! +//! ```text +//! advance :: state -> event -> (state, [action]) +//! ``` +//! +//! In short, every FSM is made up of three distinct types: +//! +//! * a state type representing all possible states of the machine +//! +//! * an event type representing all possible events in the machine +//! +//! * an action type representing a description of all possible +//! side-effects of the machine +//! +//! Using the definition above we can now say that a transition in a +//! state-machine, involving these three types, takes an initial state +//! and an event to apply it to and returns a new state and a list of +//! actions to execute. +//! +//! With this definition most processes can already be modeled quite +//! well. Two additional functions are required to make it all work: +//! +//! ```text +//! -- | The ability to cause additional side-effects after entering +//! -- a new state. +//! > enter :: state -> [action] +//! ``` +//! +//! as well as +//! +//! ```text +//! -- | An interpreter for side-effects +//! act :: action -> m [event] +//! ``` +//! +//! **Note**: This library is based on an original Haskell library. In +//! Haskell, side-effects can be controlled via the type system which +//! is impossible in Rust. +//! +//! Some parts of Finito make assumptions about the programmer not +//! making certain kinds of mistakes, which are pointed out in the +//! documentation. Unfortunately those assumptions are not +//! automatically verifiable in Rust. +//! +//! ## Example +//! +//! Please consult `finito-door` for an example representing a simple, +//! lockable door as a finite-state machine. This gives an overview +//! over Finito's primary features. +//! +//! If you happen to be the kind of person who likes to learn about +//! libraries by reading code, you should familiarise yourself with the +//! door as it shows up as the example in other finito-related +//! libraries, too. +//! +//! # Persistence, side-effects and mud +//! +//! These three things are inescapable in the fateful realm of +//! computers, but Finito separates them out into separate libraries +//! that you can drag in as you need them. +//! +//! Currently, those libraries include: +//! +//! * `finito`: Core components and classes of Finito +//! +//! * `finito-in-mem`: In-memory implementation of state machines +//! that do not need to live longer than an application using +//! standard library concurrency primitives. +//! +//! * `finito-postgres`: Postgres-backed, persistent implementation +//! of state machines that, well, do need to live longer. Uses +//! Postgres for concurrency synchronisation, so keep that in +//! mind. +//! +//! Which should cover most use-cases. Okay, enough prose, lets dive +//! in. +//! +//! # Does Finito make you want to scream? +//! +//! Please reach out! I want to know why! + +extern crate serde; + +use serde::Serialize; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::mem; + +/// Primary trait that needs to be implemented for every state type +/// representing the states of an FSM. +/// +/// This trait is used to implement transition logic and to "tie the +/// room together", with the room being our triplet of types. +pub trait FSM where Self: Sized { + /// A human-readable string uniquely describing what this FSM + /// models. This is used in log messages, database tables and + /// various other things throughout Finito. + const FSM_NAME: &'static str; + + /// The associated event type of an FSM represents all possible + /// events that can occur in the state-machine. + type Event; + + /// The associated action type of an FSM represents all possible + /// actions that can occur in the state-machine. + type Action; + + /// The associated error type of an FSM represents failures that + /// can occur during action processing. + type Error: Debug; + + /// The associated state type of an FSM describes the state that + /// is made available to the implementation of action + /// interpretations. + type State; + + /// `handle` deals with any incoming events to cause state + /// transitions and emit actions. This function is the core logic + /// of any state machine. + /// + /// Implementations of this function **must not** cause any + /// side-effects to avoid breaking the guarantees of Finitos + /// conceptual model. + fn handle(self, event: Self::Event) -> (Self, Vec<Self::Action>); + + /// `enter` is called when a new state is entered, allowing a + /// state to produce additional side-effects. + /// + /// This is useful for side-effects that event handlers do not + /// need to know about and for resting assured that a certain + /// action has been caused when a state is entered. + /// + /// FSM state types are expected to be enum (i.e. sum) types. A + /// state is considered "new" and enter calls are run if is of a + /// different enum variant. + fn enter(&self) -> Vec<Self::Action>; + + /// `act` interprets and executes FSM actions. This is the only + /// part of an FSM in which side-effects are allowed. + fn act(Self::Action, &Self::State) -> Result<Vec<Self::Event>, Self::Error>; +} + +/// This function is the primary function used to advance a state +/// machine. It takes care of both running the event handler as well +/// as possible state-enter calls and returning the result. +/// +/// Users of Finito should basically always use this function when +/// advancing state-machines manually, and never call FSM-trait +/// methods directly. +pub fn advance<S: FSM>(state: S, event: S::Event) -> (S, Vec<S::Action>) { + // Determine the enum variant of the initial state (used to + // trigger enter calls). + let old_discriminant = mem::discriminant(&state); + + let (new_state, mut actions) = state.handle(event); + + // Compare the enum variant of the resulting state to the old one + // and run `enter` if they differ. + let new_discriminant = mem::discriminant(&new_state); + let mut enter_actions = if old_discriminant != new_discriminant { + new_state.enter() + } else { + vec![] + }; + + actions.append(&mut enter_actions); + + (new_state, actions) +} + +/// This trait is implemented by Finito backends. Backends are +/// expected to be able to keep track of the current state of an FSM +/// and retrieve it / apply updates transactionally. +/// +/// See the `finito-postgres` and `finito-in-mem` crates for example +/// implementations of this trait. +/// +/// Backends must be parameterised over an additional (user-supplied) +/// state type which can be used to track application state that must +/// be made available to action handlers, for example to pass along +/// database connections. +pub trait FSMBackend<S: 'static> { + /// Key type used to identify individual state machines in this + /// backend. + /// + /// TODO: Should be parameterised over FSM type after rustc + /// #44265. + type Key; + + /// Error type for all potential failures that can occur when + /// interacting with this backend. + type Error: Debug; + + /// Insert a new state-machine into the backend's storage and + /// return its newly allocated key. + fn insert_machine<F>(&self, initial: F) -> Result<Self::Key, Self::Error> + where F: FSM + Serialize + DeserializeOwned; + + /// Retrieve the current state of an FSM by its key. + fn get_machine<F: FSM>(&self, key: Self::Key) -> Result<F, Self::Error> + where F: FSM + Serialize + DeserializeOwned; + + /// Advance a state machine by applying an event and persisting it + /// as well as any resulting actions. + /// + /// **Note**: Whether actions are automatically executed depends + /// on the backend used. Please consult the backend's + /// documentation for details. + fn advance<'a, F: FSM>(&'a self, key: Self::Key, event: F::Event) -> Result<F, Self::Error> + where F: FSM + Serialize + DeserializeOwned, + F::State: From<&'a S>, + F::Event: Serialize + DeserializeOwned, + F::Action: Serialize + DeserializeOwned; +} diff --git a/users/tazjin/finito/finito-door/Cargo.toml b/users/tazjin/finito/finito-door/Cargo.toml new file mode 100644 index 000000000000..32c0a5a7c4ef --- /dev/null +++ b/users/tazjin/finito/finito-door/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "finito-door" +version = "0.1.0" +authors = ["Vincent Ambo <mail@tazj.in>"] + +[dependencies] +failure = "0.1" +serde = "1.0" +serde_derive = "1.0" + +[dependencies.finito] +path = "../finito-core" diff --git a/users/tazjin/finito/finito-door/src/lib.rs b/users/tazjin/finito/finito-door/src/lib.rs new file mode 100644 index 000000000000..68542c0bc448 --- /dev/null +++ b/users/tazjin/finito/finito-door/src/lib.rs @@ -0,0 +1,327 @@ +//! Example implementation of a lockable door in Finito +//! +//! # What & why? +//! +//! This module serves as a (hopefully simple) example of how to +//! implement finite-state machines using Finito. Note that the +//! concepts of Finito itself won't be explained in detail here, +//! consult its library documentation for that. +//! +//! Reading through this module should give you a rough idea of how to +//! work with Finito and get you up and running modeling things +//! *quickly*. +//! +//! Note: The generated documentation for this module will display the +//! various components of the door, but it will not inform you about +//! the actual transition logic and all that stuff. Read the source, +//! too! +//! +//! # The Door +//! +//! My favourite example when explaining these state-machines +//! conceptually has been to use a simple, lockable door. Our door has +//! a keypad next to it which can be used to lock the door by entering +//! a code, after which the same code must be entered to unlock it +//! again. +//! +//! The door can only be locked if it is closed. Oh, and it has a few +//! extra features: +//! +//! * whenever the door's state changes, an IRC channel receives a +//! message about that +//! +//! * the door calls the police if the code is intered incorrectly more +//! than a specified number of times (mhm, lets say, three) +//! +//! * if the police is called the door can not be interacted with +//! anymore (and honestly, for the sake of this example, we don't +//! care how its functionality is restored) +//! +//! ## The Door - Visualized +//! +//! Here's a rough attempt at drawing a state diagram in ASCII. The +//! bracketed words denote states, the arrows denote events: +//! +//! ```text +//! <--Open--- <--Unlock-- correct code? --Unlock--> +//! [Opened] [Closed] [Locked] [Disabled] +//! --Close--> ----Lock--> +//! ``` +//! +//! I'm so sorry for that drawing. +//! +//! ## The Door - Usage example +//! +//! An interaction session with our final door could look like this: +//! +//! ```rust,ignore +//! use finito_postgres::{insert_machine, advance}; +//! +//! let door = insert_machine(&conn, &DoorState::Opened)?; +//! +//! advance(&conn, &door, DoorEvent::Close)?; +//! advance(&conn, &door, DoorEvent::Lock(1337))?; +//! +//! format!("Door is now: {}", get_machine(&conn, &door)?); +//! ``` +//! +//! Here we have created, closed and then locked a door and inspected +//! its state. We will see that it is locked, has the locking code we +//! gave it and three remaining attempts to open it. +//! +//! Alright, enough foreplay, lets dive in! + +#[macro_use] extern crate serde_derive; + +extern crate failure; +extern crate finito; + +use finito::FSM; + +/// Type synonym to represent the code with which the door is locked. This +/// exists only for clarity in the signatures below and please do not email me +/// about the fact that an integer is not actually a good representation of +/// numerical digits. Thanks! +type Code = usize; + +/// Type synonym to represent the remaining number of unlock attempts. +type Attempts = usize; + +/// This type represents the possible door states and the data that they carry. +/// We can infer this from the "diagram" in the documentation above. +/// +/// This type is the one for which `finito::FSM` will be implemented, making it +/// the wooden (?) heart of our door. +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum DoorState { + /// In `Opened` state, the door is wide open and anyone who fits through can + /// go through. + Opened, + + /// In `Closed` state, the door is shut but does not prevent anyone from + /// opening it. + Closed, + + /// In `Locked` state, the door is locked and waiting for someone to enter + /// its locking code on the keypad. + /// + /// This state contains the code that the door is locked with, as well as + /// the remaining number of attempts before the door calls the police and + /// becomes unusable. + Locked { code: Code, attempts: Attempts }, + + /// This state represents a disabled door after the police has been called. + /// The police will need to unlock it manually! + Disabled, +} + +/// This type represents the events that can occur in our door, i.e. the input +/// and interactions it receives. +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum DoorEvent { + /// `Open` means someone is opening the door! + Open, + + /// `Close` means, you guessed it, the exact opposite. + Close, + + /// `Lock` means somebody has entered a locking code on the + /// keypad. + Lock(Code), + + /// `Unlock` means someone has attempted to unlock the door. + Unlock(Code), +} + +/// This type represents the possible actions, a.k.a. everything our door "does" +/// that does not just impact itself, a.k.a. side-effects. +/// +/// **Note**: This type by itself *is not* a collection of side-effects, it +/// merely describes the side-effects we want to occur (which are then +/// interpreted by the machinery later). +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum DoorAction { + /// `NotifyIRC` is used to display some kind of message on the + /// aforementioned IRC channel that is, for some reason, very interested in + /// the state of the door. + NotifyIRC(String), + + /// `CallThePolice` does what you think it does. + /// + /// **Note**: For safety reasons, causing this action is not recommended for + /// users inside the US! + CallThePolice, +} + +/// This trait implementation turns our 'DoorState' into a type actually +/// representing a finite-state machine. To implement it, we need to do three +/// main things: +/// +/// * Define what our associated `Event` and `Action` type should be +/// +/// * Define the event-handling and state-entering logic (i.e. the meat of the +/// ... door) +/// +/// * Implement the interpretation of our actions, i.e. implement actual +/// side-effects +impl FSM for DoorState { + const FSM_NAME: &'static str = "door"; + + // As you might expect, our `Event` type is 'DoorEvent' and our `Action` + // type is 'DoorAction'. + type Event = DoorEvent; + type Action = DoorAction; + type State = (); + + // For error handling, the door simply uses `failure` which provides a + // generic, chainable error type. In real-world implementations you may want + // to use a custom error type or similar. + type Error = failure::Error; + + // The implementation of `handle` provides us with the actual transition + // logic of the door. + // + // The door is conceptually not that complicated so it is relatively short. + fn handle(self, event: DoorEvent) -> (Self, Vec<DoorAction>) { + match (self, event) { + // An opened door can be closed: + (DoorState::Opened, DoorEvent::Close) => return (DoorState::Closed, vec![]), + + // A closed door can be opened: + (DoorState::Closed, DoorEvent::Open) => return (DoorState::Opened, vec![]), + + // A closed door can also be locked, in which case the locking code + // is stored with the next state and the unlock attempts default to + // three: + (DoorState::Closed, DoorEvent::Lock(code)) => { + return (DoorState::Locked { code, attempts: 3 }, vec![]) + } + + // A locked door receiving an `Unlock`-event can do several + // different things ... + (DoorState::Locked { code, attempts }, DoorEvent::Unlock(unlock_code)) => { + // In the happy case, entry of a correct code leads to the door + // becoming unlocked (i.e. transitioning back to `Closed`). + if code == unlock_code { + return (DoorState::Closed, vec![]); + } + + // If the code wasn't correct and the fraudulent unlocker ran + // out of attempts (i.e. there was only one attempt remaining), + // it's time for some consequences. + if attempts == 1 { + return (DoorState::Disabled, vec![DoorAction::CallThePolice]); + } + + // If the code wasn't correct, but there are still some + // remaining attempts, the user doesn't have to face the police + // quite yet but IRC gets to laugh about it. + return ( + DoorState::Locked { + code, + attempts: attempts - 1, + }, + vec![DoorAction::NotifyIRC("invalid code entered".into())], + ); + } + + // This actually already concludes our event-handling logic. Our + // uncaring door does absolutely nothing if you attempt to do + // something with it that it doesn't support, so the last handler is + // a simple fallback. + // + // In a real-world state machine, especially one that receives + // events from external sources, you may want fallback handlers to + // actually do something. One example could be creating an action + // that logs information about unexpected events, alerts a + // monitoring service, or whatever else. + (current, _) => (current, vec![]), + } + } + + // The implementation of `enter` lets door states cause additional actions + // they are transitioned to. In the door example we use this only to notify + // IRC about what is going on. + fn enter(&self) -> Vec<DoorAction> { + let msg = match self { + DoorState::Opened => "door was opened", + DoorState::Closed => "door was closed", + DoorState::Locked { .. } => "door was locked", + DoorState::Disabled => "door was disabled", + }; + + vec![DoorAction::NotifyIRC(msg.into())] + } + + // The implementation of `act` lets us perform actual side-effects. + // + // Again, for the sake of educational simplicity, this does not deal with + // all potential (or in fact any) error cases that can occur during this toy + // implementation of actions. + // + // Additionally the `act` function can return new events. This is useful for + // a sort of "callback-like" pattern (cause an action to fetch some data, + // receive it as an event) but is not used in this example. + fn act(action: DoorAction, _state: &()) -> Result<Vec<DoorEvent>, failure::Error> { + match action { + DoorAction::NotifyIRC(msg) => { + use std::fs::OpenOptions; + use std::io::Write; + + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open("/tmp/door-irc.log")?; + + write!(file, "<doorbot> {}\n", msg)?; + Ok(vec![]) + } + + DoorAction::CallThePolice => { + // TODO: call the police + println!("The police was called! For real!"); + Ok(vec![]) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use finito::advance; + + fn test_fsm<S: FSM>(initial: S, events: Vec<S::Event>) -> (S, Vec<S::Action>) { + events.into_iter().fold((initial, vec![]), |(state, mut actions), event| { + let (new_state, mut new_actions) = advance(state, event); + actions.append(&mut new_actions); + (new_state, actions) + }) + } + + #[test] + fn test_door() { + let initial = DoorState::Opened; + let events = vec![ + DoorEvent::Close, + DoorEvent::Open, + DoorEvent::Close, + DoorEvent::Lock(1234), + DoorEvent::Unlock(1234), + DoorEvent::Lock(4567), + DoorEvent::Unlock(1234), + ]; + let (final_state, actions) = test_fsm(initial, events); + + assert_eq!(final_state, DoorState::Locked { code: 4567, attempts: 2 }); + assert_eq!(actions, vec![ + DoorAction::NotifyIRC("door was closed".into()), + DoorAction::NotifyIRC("door was opened".into()), + DoorAction::NotifyIRC("door was closed".into()), + DoorAction::NotifyIRC("door was locked".into()), + DoorAction::NotifyIRC("door was closed".into()), + DoorAction::NotifyIRC("door was locked".into()), + DoorAction::NotifyIRC("invalid code entered".into()), + ]); + } +} diff --git a/users/tazjin/finito/finito-postgres/Cargo.toml b/users/tazjin/finito/finito-postgres/Cargo.toml new file mode 100644 index 000000000000..dd8d1d000304 --- /dev/null +++ b/users/tazjin/finito/finito-postgres/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "finito-postgres" +version = "0.1.0" +authors = ["Vincent Ambo <mail@tazj.in>"] + +[dependencies] +chrono = "0.4" +postgres-derive = "0.3" +serde = "1.0" +serde_json = "1.0" +r2d2_postgres = "0.14" + +[dependencies.postgres] +version = "0.15" +features = [ "with-uuid", "with-chrono", "with-serde_json" ] + +[dependencies.uuid] +version = "0.5" +features = [ "v4" ] + +[dependencies.finito] +path = "../finito-core" + +[dev-dependencies.finito-door] +path = "../finito-door" diff --git a/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/down.sql b/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/down.sql new file mode 100644 index 000000000000..9b56f9d35abe --- /dev/null +++ b/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/down.sql @@ -0,0 +1,4 @@ +DROP TABLE actions; +DROP TYPE ActionStatus; +DROP TABLE events; +DROP TABLE machines; diff --git a/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/up.sql b/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/up.sql new file mode 100644 index 000000000000..18ace393b8d9 --- /dev/null +++ b/users/tazjin/finito/finito-postgres/migrations/2018-09-26-160621_bootstrap_finito_schema/up.sql @@ -0,0 +1,37 @@ +-- Creates the initial schema required by finito-postgres. + +CREATE TABLE machines ( + id UUID PRIMARY KEY, + created TIMESTAMPTZ NOT NULL DEFAULT NOW(), + fsm TEXT NOT NULL, + state JSONB NOT NULL +); + +CREATE TABLE events ( + id UUID PRIMARY KEY, + created TIMESTAMPTZ NOT NULL DEFAULT NOW(), + fsm TEXT NOT NULL, + fsm_id UUID NOT NULL REFERENCES machines(id), + event JSONB NOT NULL +); +CREATE INDEX idx_events_machines ON events(fsm_id); + +CREATE TYPE ActionStatus AS ENUM ( + 'Pending', + 'Completed', + 'Failed' +); + +CREATE TABLE actions ( + id UUID PRIMARY KEY, + created TIMESTAMPTZ NOT NULL DEFAULT NOW(), + fsm TEXT NOT NULL, + fsm_id UUID NOT NULL REFERENCES machines(id), + event_id UUID NOT NULL REFERENCES events(id), + content JSONB NOT NULL, + status ActionStatus NOT NULL, + error TEXT +); + +CREATE INDEX idx_actions_machines ON actions(fsm_id); +CREATE INDEX idx_actions_events ON actions(event_id); diff --git a/users/tazjin/finito/finito-postgres/src/error.rs b/users/tazjin/finito/finito-postgres/src/error.rs new file mode 100644 index 000000000000..e130d18361f1 --- /dev/null +++ b/users/tazjin/finito/finito-postgres/src/error.rs @@ -0,0 +1,109 @@ +//! This module defines error types and conversions for issue that can +//! occur while dealing with persisted state machines. + +use std::result; +use std::fmt; +use uuid::Uuid; +use std::error::Error as StdError; + +// errors to chain: +use postgres::Error as PgError; +use r2d2_postgres::r2d2::Error as PoolError; +use serde_json::Error as JsonError; + +pub type Result<T> = result::Result<T, Error>; + +#[derive(Debug)] +pub struct Error { + pub kind: ErrorKind, + pub context: Option<String>, +} + +#[derive(Debug)] +pub enum ErrorKind { + /// Errors occuring during JSON serialization of FSM types. + Serialization(String), + + /// Errors occuring during communication with the database. + Database(String), + + /// Errors with the database connection pool. + DBPool(String), + + /// State machine could not be found. + FSMNotFound(Uuid), + + /// Action could not be found. + ActionNotFound(Uuid), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ErrorKind::*; + let msg = match &self.kind { + Serialization(err) => + format!("JSON serialization error: {}", err), + + Database(err) => + format!("PostgreSQL error: {}", err), + + DBPool(err) => + format!("Database connection pool error: {}", err), + + FSMNotFound(id) => + format!("FSM with ID {} not found", id), + + ActionNotFound(id) => + format!("Action with ID {} not found", id), + }; + + match &self.context { + None => write!(f, "{}", msg), + Some(ctx) => write!(f, "{}: {}", ctx, msg), + } + } +} + +impl StdError for Error {} + +impl <E: Into<ErrorKind>> From<E> for Error { + fn from(err: E) -> Error { + Error { + kind: err.into(), + context: None, + } + } +} + +impl From<JsonError> for ErrorKind { + fn from(err: JsonError) -> ErrorKind { + ErrorKind::Serialization(err.to_string()) + } +} + +impl From<PgError> for ErrorKind { + fn from(err: PgError) -> ErrorKind { + ErrorKind::Database(err.to_string()) + } +} + +impl From<PoolError> for ErrorKind { + fn from(err: PoolError) -> ErrorKind { + ErrorKind::DBPool(err.to_string()) + } +} + +/// Helper trait that makes it possible to supply contextual +/// information with an error. +pub trait ResultExt<T> { + fn context<C: fmt::Display>(self, ctx: C) -> Result<T>; +} + +impl <T, E: Into<Error>> ResultExt<T> for result::Result<T, E> { + fn context<C: fmt::Display>(self, ctx: C) -> Result<T> { + self.map_err(|err| Error { + context: Some(format!("{}", ctx)), + .. err.into() + }) + } +} diff --git a/users/tazjin/finito/finito-postgres/src/lib.rs b/users/tazjin/finito/finito-postgres/src/lib.rs new file mode 100644 index 000000000000..ae147f751f88 --- /dev/null +++ b/users/tazjin/finito/finito-postgres/src/lib.rs @@ -0,0 +1,431 @@ +//! PostgreSQL-backed persistence for Finito state machines +//! +//! This module implements ... TODO when I can write again. +//! +//! TODO: events & actions should have `SERIAL` keys + +#[macro_use] extern crate postgres; +#[macro_use] extern crate postgres_derive; + +extern crate chrono; +extern crate finito; +extern crate r2d2_postgres; +extern crate serde; +extern crate serde_json; +extern crate uuid; + +#[cfg(test)] mod tests; +#[cfg(test)] extern crate finito_door; + +mod error; +pub use error::{Result, Error, ErrorKind}; + +use chrono::prelude::{DateTime, Utc}; +use error::ResultExt; +use finito::{FSM, FSMBackend}; +use postgres::transaction::Transaction; +use postgres::GenericConnection; +use serde::Serialize; +use serde::de::DeserializeOwned; +use serde_json::Value; +use std::marker::PhantomData; +use uuid::Uuid; +use r2d2_postgres::{r2d2, PostgresConnectionManager}; + +type DBPool = r2d2::Pool<PostgresConnectionManager>; +type DBConn = r2d2::PooledConnection<PostgresConnectionManager>; + +/// This struct represents rows in the database table in which events +/// are persisted. +#[derive(Debug, ToSql, FromSql)] +struct EventT { + /// ID of the persisted event. + id: Uuid, + + /// Timestamp at which the event was stored. + created: DateTime<Utc>, + + /// Name of the type of FSM that this state belongs to. + fsm: String, + + /// ID of the state machine belonging to this event. + fsm_id: Uuid, + + /// Serialised content of the event. + event: Value, +} + +/// This enum represents the possible statuses an action can be in. +#[derive(Debug, PartialEq, ToSql, FromSql)] +#[postgres(name = "actionstatus")] +enum ActionStatus { + /// The action was requested but has not run yet. + Pending, + + /// The action completed successfully. + Completed, + + /// The action failed to run. Information about the error will + /// have been persisted in Postgres. + Failed, +} + +/// This struct represents rows in the database table in which actions +/// are persisted. +#[derive(Debug, ToSql, FromSql)] +struct ActionT { + /// ID of the persisted event. + id: Uuid, + + /// Timestamp at which the event was stored. + created: DateTime<Utc>, + + /// Name of the type of FSM that this state belongs to. + fsm: String, + + /// ID of the state machine belonging to this event. + fsm_id: Uuid, + + /// ID of the event that resulted in this action. + event_id: Uuid, + + /// Serialised content of the action. + #[postgres(name = "content")] // renamed because 'action' is a keyword in PG + action: Value, + + /// Current status of the action. + status: ActionStatus, + + /// Detailed (i.e. Debug-trait formatted) error message, if an + /// error occured during action processing. + error: Option<String>, +} + +// The following functions implement the public interface of +// `finito-postgres`. + +/// TODO: Write docs for this type, brain does not want to do it right +/// now. +pub struct FinitoPostgres<S> { + state: S, + + db_pool: DBPool, +} + +impl <S> FinitoPostgres<S> { + pub fn new(state: S, db_pool: DBPool, _pool_size: usize) -> Self { + FinitoPostgres { + state, db_pool, + } + } +} + +impl <State: 'static> FSMBackend<State> for FinitoPostgres<State> { + type Key = Uuid; + type Error = Error; + + fn insert_machine<S: FSM + Serialize>(&self, initial: S) -> Result<Uuid> { + let query = r#" + INSERT INTO machines (id, fsm, state) + VALUES ($1, $2, $3) + "#; + + let id = Uuid::new_v4(); + let fsm = S::FSM_NAME.to_string(); + let state = serde_json::to_value(initial).context("failed to serialise FSM")?; + + self.conn()?.execute(query, &[&id, &fsm, &state]).context("failed to insert FSM")?; + + return Ok(id); + + } + + fn get_machine<S: FSM + DeserializeOwned>(&self, key: Uuid) -> Result<S> { + get_machine_internal(&*self.conn()?, key, false) + } + + /// Advance a persisted state machine by applying an event, and + /// storing the event as well as all resulting actions. + /// + /// This function holds a database-lock on the state's row while + /// advancing the machine. + /// + /// **Note**: This function returns the new state of the machine + /// immediately after applying the event, however this does not + /// necessarily equate to the state of the machine after all related + /// processing is finished as running actions may result in additional + /// transitions. + fn advance<'a, S>(&'a self, key: Uuid, event: S::Event) -> Result<S> + where S: FSM + Serialize + DeserializeOwned, + S::State: From<&'a State>, + S::Event: Serialize + DeserializeOwned, + S::Action: Serialize + DeserializeOwned { + let conn = self.conn()?; + let tx = conn.transaction().context("could not begin transaction")?; + let state = get_machine_internal(&tx, key, true)?; + + // Advancing the FSM consumes the event, so it is persisted first: + let event_id = insert_event::<_, S>(&tx, key, &event)?; + + // Core advancing logic is run: + let (new_state, actions) = finito::advance(state, event); + + // Resulting actions are persisted (TODO: and interpreted) + let mut action_ids = vec![]; + for action in actions { + let action_id = insert_action::<_, S>(&tx, key, event_id, &action)?; + action_ids.push(action_id); + } + + // And finally the state is updated: + update_state(&tx, key, &new_state)?; + tx.commit().context("could not commit transaction")?; + + self.run_actions::<S>(key, action_ids); + + Ok(new_state) + } +} + +impl <State: 'static> FinitoPostgres<State> { + /// Execute several actions at the same time, each in a separate + /// thread. Note that actions returning further events, causing + /// further transitions, returning further actions and so on will + /// potentially cause multiple threads to get created. + fn run_actions<'a, S>(&'a self, fsm_id: Uuid, action_ids: Vec<Uuid>) where + S: FSM + Serialize + DeserializeOwned, + S::Event: Serialize + DeserializeOwned, + S::Action: Serialize + DeserializeOwned, + S::State: From<&'a State> { + let state: S::State = (&self.state).into(); + let conn = self.conn().expect("TODO"); + + for action_id in action_ids { + let tx = conn.transaction().expect("TODO"); + + // TODO: Determine which concurrency setup we actually want. + if let Ok(events) = run_action(tx, action_id, &state, PhantomData::<S>) { + for event in events { + self.advance::<S>(fsm_id, event).expect("TODO"); + } + } + } + } + + /// Retrieve a single connection from the database connection pool. + fn conn(&self) -> Result<DBConn> { + self.db_pool.get().context("failed to retrieve connection from pool") + } +} + + + +/// Insert a single state-machine into the database and return its +/// newly allocated, random UUID. +pub fn insert_machine<C, S>(conn: &C, initial: S) -> Result<Uuid> where + C: GenericConnection, + S: FSM + Serialize { + let query = r#" + INSERT INTO machines (id, fsm, state) + VALUES ($1, $2, $3) + "#; + + let id = Uuid::new_v4(); + let fsm = S::FSM_NAME.to_string(); + let state = serde_json::to_value(initial).context("failed to serialize FSM")?; + + conn.execute(query, &[&id, &fsm, &state])?; + + return Ok(id); +} + +/// Insert a single event into the database and return its UUID. +fn insert_event<C, S>(conn: &C, + fsm_id: Uuid, + event: &S::Event) -> Result<Uuid> +where + C: GenericConnection, + S: FSM, + S::Event: Serialize { + let query = r#" + INSERT INTO events (id, fsm, fsm_id, event) + VALUES ($1, $2, $3, $4) + "#; + + let id = Uuid::new_v4(); + let fsm = S::FSM_NAME.to_string(); + let event_value = serde_json::to_value(event) + .context("failed to serialize event")?; + + conn.execute(query, &[&id, &fsm, &fsm_id, &event_value])?; + return Ok(id) +} + +/// Insert a single action into the database and return its UUID. +fn insert_action<C, S>(conn: &C, + fsm_id: Uuid, + event_id: Uuid, + action: &S::Action) -> Result<Uuid> where + C: GenericConnection, + S: FSM, + S::Action: Serialize { + let query = r#" + INSERT INTO actions (id, fsm, fsm_id, event_id, content, status) + VALUES ($1, $2, $3, $4, $5, $6) + "#; + + let id = Uuid::new_v4(); + let fsm = S::FSM_NAME.to_string(); + let action_value = serde_json::to_value(action) + .context("failed to serialize action")?; + + conn.execute( + query, + &[&id, &fsm, &fsm_id, &event_id, &action_value, &ActionStatus::Pending] + )?; + + return Ok(id) +} + +/// Update the state of a specified machine. +fn update_state<C, S>(conn: &C, + fsm_id: Uuid, + state: &S) -> Result<()> where + C: GenericConnection, + S: FSM + Serialize { + let query = r#" + UPDATE machines SET state = $1 WHERE id = $2 + "#; + + let state_value = serde_json::to_value(state).context("failed to serialize FSM")?; + let res_count = conn.execute(query, &[&state_value, &fsm_id])?; + + if res_count != 1 { + Err(ErrorKind::FSMNotFound(fsm_id).into()) + } else { + Ok(()) + } +} + +/// Conditionally alter SQL statement to append locking clause inside +/// of a transaction. +fn alter_for_update(alter: bool, query: &str) -> String { + match alter { + false => query.to_string(), + true => format!("{} FOR UPDATE", query), + } +} + +/// Retrieve the current state of a state machine from the database, +/// optionally locking the machine state for the duration of some +/// enclosing transaction. +fn get_machine_internal<C, S>(conn: &C, + id: Uuid, + for_update: bool) -> Result<S> where + C: GenericConnection, + S: FSM + DeserializeOwned { + let query = alter_for_update(for_update, r#" + SELECT state FROM machines WHERE id = $1 + "#); + + let rows = conn.query(&query, &[&id]).context("failed to retrieve FSM")?; + + if let Some(row) = rows.into_iter().next() { + Ok(serde_json::from_value(row.get(0)).context("failed to deserialize FSM")?) + } else { + Err(ErrorKind::FSMNotFound(id).into()) + } +} + +/// Retrieve an action from the database, optionally locking it for +/// the duration of some enclosing transaction. +fn get_action<C, S>(conn: &C, id: Uuid) -> Result<(ActionStatus, S::Action)> where + C: GenericConnection, + S: FSM, + S::Action: DeserializeOwned { + let query = alter_for_update(true, r#" + SELECT status, content FROM actions + WHERE id = $1 AND fsm = $2 + "#); + + let rows = conn.query(&query, &[&id, &S::FSM_NAME])?; + + if let Some(row) = rows.into_iter().next() { + let action = serde_json::from_value(row.get(1)) + .context("failed to deserialize FSM action")?; + Ok((row.get(0), action)) + } else { + Err(ErrorKind::ActionNotFound(id).into()) + } +} + +/// Update the status of an action after an attempt to run it. +fn update_action_status<C, S>(conn: &C, + id: Uuid, + status: ActionStatus, + error: Option<String>, + _fsm: PhantomData<S>) -> Result<()> where + C: GenericConnection, + S: FSM { + let query = r#" + UPDATE actions SET status = $1, error = $2 + WHERE id = $3 AND fsm = $4 + "#; + + let result = conn.execute(&query, &[&status, &error, &id, &S::FSM_NAME])?; + + if result != 1 { + Err(ErrorKind::ActionNotFound(id).into()) + } else { + Ok(()) + } +} + +/// Execute a single action in case it is pending or retryable. Holds +/// a lock on the action's database row while performing the action +/// and writes back the status afterwards. +/// +/// Should the execution of an action fail cleanly (i.e. without a +/// panic), the error will be persisted. Should it fail by panicking +/// (which developers should never do explicitly in action +/// interpreters) its status will not be changed. +fn run_action<S>(tx: Transaction, id: Uuid, state: &S::State, _fsm: PhantomData<S>) + -> Result<Vec<S::Event>> where + S: FSM, + S::Action: DeserializeOwned { + let (status, action) = get_action::<Transaction, S>(&tx, id)?; + + let result = match status { + ActionStatus::Pending => { + match S::act(action, state) { + // If the action succeeded, update its status to + // completed and return the created events. + Ok(events) => { + update_action_status( + &tx, id, ActionStatus::Completed, None, PhantomData::<S> + )?; + events + }, + + // If the action failed, persist the debug message and + // return nothing. + Err(err) => { + let msg = Some(format!("{:?}", err)); + update_action_status( + &tx, id, ActionStatus::Failed, msg, PhantomData::<S> + )?; + vec![] + }, + } + }, + + _ => { + // TODO: Currently only pending actions are run because + // retryable actions are not yet implemented. + vec![] + }, + }; + + tx.commit().context("failed to commit transaction")?; + Ok(result) +} diff --git a/users/tazjin/finito/finito-postgres/src/tests.rs b/users/tazjin/finito/finito-postgres/src/tests.rs new file mode 100644 index 000000000000..b1b5821be3c4 --- /dev/null +++ b/users/tazjin/finito/finito-postgres/src/tests.rs @@ -0,0 +1,47 @@ +use super::*; + +use finito_door::*; +use postgres::{Connection, TlsMode}; + +// TODO: read config from environment +fn open_test_connection() -> Connection { + Connection::connect("postgres://finito:finito@localhost/finito", TlsMode::None) + .expect("Failed to connect to test database") +} + +#[test] +fn test_insert_machine() { + let conn = open_test_connection(); + let initial = DoorState::Opened; + let door = insert_machine(&conn, initial).expect("Failed to insert door"); + let result = get_machine(&conn, &door, false).expect("Failed to fetch door"); + + assert_eq!(result, DoorState::Opened, "Inserted door state should match"); +} + +#[test] +fn test_advance() { + let conn = open_test_connection(); + + let initial = DoorState::Opened; + let events = vec![ + DoorEvent::Close, + DoorEvent::Open, + DoorEvent::Close, + DoorEvent::Lock(1234), + DoorEvent::Unlock(1234), + DoorEvent::Lock(4567), + DoorEvent::Unlock(1234), + ]; + + let door = insert_machine(&conn, initial).expect("Failed to insert door"); + + for event in events { + advance(&conn, &door, event).expect("Failed to advance door FSM"); + } + + let result = get_machine(&conn, &door, false).expect("Failed to fetch door"); + let expected = DoorState::Locked { code: 4567, attempts: 2 }; + + assert_eq!(result, expected, "Advanced door state should match"); +} diff --git a/users/tazjin/gruber-darker.qss b/users/tazjin/gruber-darker.qss new file mode 100644 index 000000000000..16f4c2f32991 --- /dev/null +++ b/users/tazjin/gruber-darker.qss @@ -0,0 +1,508 @@ +/** +** Gruber Darker theme for Quassel. +** +** This theme derives from multiple different things: +** +** - Quassel DarkSolarized (https://gist.github.com/Zren/e91ad5197f9d6b6d410f) +** - Quassel Dracula (https://github.com/dracula/quassel) +** - gruber-darker for Emacs (https://github.com/rexim/gruber-darker-theme) +** - Original Gruber theme for BBEdit (https://daringfireball.net/projects/bbcolors/schemes/) +** +** This is a work-in-progress as I haven't figured out the point of +** all of the colours yet, and what I want them to be instead. +** +**/ + +/** +** Helpful Links: +** - QT: +** http://qt-project.org/doc/qt-4.8/stylesheet-syntax.html +** http://doc.qt.nokia.com/4.7-snapshot/stylesheet-reference.html +** http://doc.qt.nokia.com/4.7-snapshot/stylesheet-examples.html +** - Plastique Client Style: +** https://qt.gitorious.org/qt/qt/source/src/gui/styles/qplastiquestyle.cpp +** https://github.com/mirror/qt/blob/4.8/src/gui/styles/qplastiquestyle.cpp +** - Quassel Stylesheet Gallery: +** http://bugs.quassel-irc.org/projects/1/wiki/Stylesheet_Gallery +** http://bugs.quassel-irc.org/projects/1/wiki/Stylesheet_Gallery#DarkMonokaiqss +*/ + +/** +** - QSS Notes: +** Quassel stylesheets also support Palette { role: color; } for setting the system +** palette. See the QPalette docs for available roles, and convert them into qss-style +** attributes, so ButtonText would become button-text or see qssparser.cpp In fact, +** qssparser.cpp is the authorative source for Quassel's qss syntax that contains all +** the extensions over standard Qt qss syntax. +** See: +** http://qt-project.org/doc/qt-4.8/qpalette.html#ColorRole-enum +** https://github.com/quassel/quassel/blob/master/src/uisupport/qssparser.cpp +** +*/ + +Palette { + /* Window colors */ + window: #282828; + background: #181818; + foreground: #f4f4f4; + + base: #181818; + alternate-base: #282828; + + /* Just setting palette(tooltip-base) doesn't work as intended so we set it in + ** a QTooltip{} rule as well. + */ + tooltip-base: #282a36; // palette(base) TODO + tooltip-text: white; // palette(text) TODO + + /* The following attributes should be done in a scale */ + light: #444444; // Tab Borders, Scrollbar handle grips, Titled Panel border (Settings) + midlight: #333333; // ? + button: #292929; // Menu BG, Scrollbar and Button base. + mid: #252525; // Titled Panel border (Settings) + dark: #202020; // TreeView [-] and ... color (Also various borders in Windows Client Style) + shadow: #1d1d1d; // ? + + + /* Text colors */ + text: white; + button-text: #f8f8f2; + + highlight: #44475a; + + /* Link colors */ + link: #ff79c6; + link-visited: #bd93f9; + + /* Color of the marker line in the chat view. BG Node that is overlayed on the first new ChatLine. */ + // 0 -> 0.1 (sharp line) + marker-line: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #586e75, stop: 0.1 #586e75, stop: 0.1 transparent); +} + +/* +** Base Object Colors +*/ + +/* Tables */ +// QTreeView#settingsTree -> Tree in the Settings popup. + +QTreeView, QTableView { + alternate-background-color: #282a36; + // background-color: palette(shadow); + border: 0px; +} + +QTreeView { + selection-background-color: transparent; +} + +QTreeView::item { + border-left: 2px solid palette(base); +} + +QTreeView::item:focus { + border-width: 0 0 0 2px; + outline: none; +} + +QTreeView::item:selected { + border-width: 0 0 0 2px; + color: palette(button-text); +} + +QTreeView::item:hover { + background: palette(dark); +} + + +QTreeView::item:selected:active{ + color: palette(button-text); + background: palette(dark); + border-color: palette(highlight); +} + +QTreeView::item:selected:!active { + color: palette(button-text); + background: palette(dark); + border-color: palette(highlight); +} + +/* Scrollbar */ +/* From Quassel Wiki: http://sprunge.us/iZGB */ +QScrollBar { + //background: transparent; + background: palette(base); + margin: 0; +} +QScrollBar:hover { + /* Optional: Subtle accent of scrolling area on hover */ + background: #161616; /* base +2 */ +} +QScrollBar:vertical { + width: 8px; +} +QScrollBar:horizontal { + height: 8px; +} + +QScrollBar::handle { + padding: 0; + margin: 2px; + border-radius: 2px; + border: 2px solid palette(midlight); + background: palette(midlight); +} + +QScrollBar::handle:vertical { + min-height: 20px; + min-width: 0px; +} + +QScrollBar::handle:horizontal { + min-width: 20px; + min-height: 0px; +} +QScrollBar::handle:hover { + border-color: palette(light); + background: palette(light); +} +QScrollBar::handle:pressed { + background: palette(highlight); + border-color: palette(highlight); +} + +QScrollBar::add-line , QScrollBar::sub-line { + height: 0px; + border: 0px; +} +QScrollBar::up-arrow, QScrollBar::down-arrow { + border: 0px; + width: 0px; + height: 0px; +} + +QScrollBar::add-page, QScrollBar::sub-page { + background: none; +} + +/* Input Box */ +MultiLineEdit { + //background: palette(base); + //color: palette(foreground); +} + +/* Widgets */ +/* http://doc.qt.nokia.com/4.7-snapshot/qdockwidget.html */ +//QMainWindow, +QMainWindow QAbstractScrollArea { + //border: 0; // Remove borders. + border: 1px solid palette(shadow); +} + +QMainWindow { + //background: palette(mid); // Main window trim +} + +/* Splitter */ +/* The splits between QDockWidgets and QMainWindow is a different element. */ +QSplitter::handle, +QMainWindow::separator { + background: palette(dark); +} +QSplitter::handle:horizontal:hover, +QMainWindow::separator:vertical:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 palette(window), stop: 0.5 palette(light), stop: 1 palette(window)); +} + +QSplitter::handle:vertical:hover, +QMainWindow::separator:horizontal:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(window), stop: 0.5 palette(light), stop: 1 palette(window)); +} + +/* Menu Bar / Context Menues */ +QMenu { + margin: 5px; // A bit of nice padding around menu items. +} + +/* ToolTip */ +/* Note: You cannot create transparent sections in the popup box without a mask set. Thus the black edges outside the rounded borders. */ +QToolTip { + border: 2px solid #202020; // palette(dark) + border-radius: 2px; + background: #282a36; // palette(base) + color: white; // palette(text) +} + +/* Tabs */ +/* + The palette is designed for the selected one to be darker. So we need to change it. Decided to do a simple line. + tab:bottom and tab:top reverse y1 and y2 on the linear gradients. + + Tab Shadow: #444444 (light) + Tab Hover: #666 + Tab Selected: palette(highlight) +*/ + +QTabWidget::tab-bar { + alignment: center; +} + +QTabBar::tab { + min-width: 30px; + height: 20px; +} + +QTabBar::tab:bottom:selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(highlight), stop: 0.2 palette(highlight), stop: 0.2 transparent); +} + +QTabBar::tab:top:selected { + background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 palette(highlight), stop: 0.2 palette(highlight), stop: 0.2 transparent); +} + +QTabBar::tab:!selected { + color: #888; +} + +QTabBar::tab:bottom:!selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(light), stop: 0.2 palette(light), stop: 0.2 transparent); +} + +QTabBar::tab:top:!selected { + background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 palette(light), stop: 0.2 palette(light), stop: 0.2 transparent); +} + +QTabBar::tab:!selected:hover { + color: #aaa; +} + +QTabBar::tab:bottom:!selected:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #666, stop: 0.2 #666, stop: 0.2 transparent); +} + +QTabBar::tab:top:!selected:hover { + background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #666, stop: 0.2 #666, stop: 0.2 transparent); +} + +/* +** Quassel CSS +*/ + +/* Main Chat Background Override */ +ChatView { + background: #181818; +} +ChatView QScrollBar { + background: #282a36; +} +ChatView QScrollBar:hover { + background: #282a36; +} + +ChatView QScrollBar::handle { + border-color: #44475a; + background: #44475a; +} + +ChatView QScrollBar::handle:hover { + border-color: #44475a; + background: #44475a; +} + +/**/ +QStatusBar {} +QStatusBar::item { + border: none; +} +QStatusBar QLabel { + color: #888; +} + +/* https://github.com/quassel/quassel/blob/master/src/qtui/ui/msgprocessorstatuswidget.ui */ +QStatusBar MsgProcessorStatusWidget {} +QStatusBar MsgProcessorStatusWidget QLabel#label {} +QStatusBar MsgProcessorStatusWidget QProgressBar#progressBar {} + +/* https://github.com/quassel/quassel/blob/master/src/qtui/ui/coreconnectionstatuswidget.ui */ +QStatusBar CoreConnectionStatusWidget {} +QStatusBar CoreConnectionStatusWidget QLabel#messageLabel {} +QStatusBar CoreConnectionStatusWidget QProgressBar#progressBar {} +QStatusBar CoreConnectionStatusWidget QLabel#lagLabel {} +QStatusBar CoreConnectionStatusWidget QLabel#sslLabel { + qproperty-pixmap: none; /* Hide the SSL status icon */ +} + + +/* Font */ +// Will not override if selectors are doubled up eg: "ChatLine, MultiLineEdit {}" +// These will override anything set in Quassel's Settings. +/** + * Don't bold or style MultiLineEdit text in any way otherwise you will be + * prone to get weird behaviour in submitting from the Input box. + * It will randomly bold your input if you do. + */ +ChatLine { + //font-family: "MingLiU_HKSCS-ExtB", "Courier New", Courier, Monotype; + + //font-size: 13pt; + //font-weight: bold; + } +MultiLineEdit { + //font-family: "MingLiU_HKSCS-ExtB", "Courier New", Courier, Monotype; + + //font-size: 20px; + //font-weight: normal; + } +ChatLine#plain { + //font-weight: bold; + } + +/* Font: UI Global Font */ +QWidget { + //font-family: consolas; + } +ChatListItem { + font-family: consolas; + } +NickListItem { + font-family: consolas; + } +StyledLabel#topicLabel { + font-family: consolas; + font-size: 14px; + } + + +/* Topic Box */ +StyledLabel#topicLabel { background: palette(base); font-family: consolas; } + +/* Buffer / Channel List */ +/** + state: inactive, channel-event, unread-message, highlighted + type: query, channel, network +**/ +ChatListItem { foreground: #f8f8f2; } +ChatListItem[state="inactive"] { foreground: #44475a; } +ChatListItem[state="channel-event"] { foreground: #6272a4; } /* palette(button-text) */ +ChatListItem[state="unread-message"] { foreground: #f8f8f2; } +ChatListItem[state="highlighted"] { foreground: #44475a; } + +ChatListItem[type="network", state="unread-message"] { foreground: #44475a; } +ChatListItem[type="network", state="highlighted"] { foreground: #44475a; } +ChatListItem[type="query", state="unread-message"] { foreground: #44475a; } + + +/* Nick List */ +/** + state: away + type: user, category +**/ +NickListItem[type="category"] { foreground: #6272a4; } +NickListItem[type="user"] { foreground: #f8f8f2 } +NickListItem[type="user", state="away"] { foreground: #44475a; } + + + +/* Chatbox Line Formatting */ +ChatLine[label="highlight"] { + foreground: #f5f5f5; + background: #282828; +} + +/* +** Option: Bold highlighted text, but not the timestamp. +*/ +/* +ChatLine[label="highlight"] { font-weight: bold; } +ChatLine::timestamp[label="highlight"]{ font-weight: normal; } +*/ + +ChatLine::timestamp[label="highlight"] { foreground: #44475a; } + +ChatLine::timestamp { } + +/* ::contents == Message */ +ChatLine::contents { + /* Can only set background */ +} + +ChatLine#plain { foreground: #f8f8f2; } +ChatLine#notice { foreground: #44475a; } +ChatLine#action { foreground: #565f73; font-style: italic; font-weight: bold; } +ChatLine#nick { foreground: #6272a4; } +ChatLine#mode { foreground: #6272a4; } +ChatLine#join { foreground: #6272a4; } +ChatLine#part { foreground: #6272a4; } +ChatLine#quit { foreground: #6272a4; } +ChatLine#kick { foreground: #6272a4; } +ChatLine#kill { foreground: #6272a4; } +ChatLine#server { foreground: #44475a; } +ChatLine#info { foreground: #44475a; } +ChatLine#error { foreground: #ff5555; } +ChatLine#daychange { foreground: #44475a; } +ChatLine#topic { foreground: #f1fa8c; } +ChatLine#netsplit-join { foreground: #44475a; } +ChatLine#netsplit-quit { foreground: #44475a; } + +ChatLine::timestamp { + foreground: #586e75; + // Resets the timestemp font during #action and other possible formatting. + font-style: normal; + font-weight: normal; +} + +ChatLine::url { + foreground: palette(link); + //font-style: underline; // Uncomment if you always want an underline on links. +} + +/* Sender Colors */ +ChatLine::sender#plain[sender="self"] { foreground: #586e75; } + +/** + * The following are the sixteen colours used for the senders. + * The names are calculated by taking the hash of the nickname. + * Then take the modulo (the remainder) when divided by 16. + * Preview: http://i.imgur.com/xeRKI4H.png + */ +ChatLine::sender#plain[sender="0"] { foreground: #96a6c8; } +ChatLine::sender#plain[sender="1"] { foreground: #73c936; } +ChatLine::sender#plain[sender="2"] { foreground: #ffdd33; } +ChatLine::sender#plain[sender="3"] { foreground: #cc8c3c; } +ChatLine::sender#plain[sender="4"] { foreground: #ff4f58; } +ChatLine::sender#plain[sender="5"] { foreground: #9e95c7; } +ChatLine::sender#plain[sender="6"] { foreground: #95a99f; } +ChatLine::sender#plain[sender="7"] { foreground: #8be9fd; } + +/* +32 */ +ChatLine::sender#plain[sender="8"] { foreground: #96a6c8; } +ChatLine::sender#plain[sender="9"] { foreground: #73c936; } +ChatLine::sender#plain[sender="a"] { foreground: #ffdd33; } +ChatLine::sender#plain[sender="b"] { foreground: #cc8c3c; } +ChatLine::sender#plain[sender="c"] { foreground: #ff4f58; } +ChatLine::sender#plain[sender="d"] { foreground: #9e95c7; } +ChatLine::sender#plain[sender="e"] { foreground: #95a99f; } +ChatLine::sender#plain[sender="f"] { foreground: #8be9fd; } + +/* +** mIRC formats +*/ +ChatLine[format="bold"] { font-weight: bold;} +ChatLine[format="italic"] { font-style: italic; } +ChatLine[format="underline"] { font-style: underline; } + +/* Blues are hard to read. */ +ChatLine[fg-color="2"] { foreground: #15a; } +ChatLine[bg-color="2"] { background: #15a; } +ChatLine[fg-color="c"] { foreground: #15f; } +ChatLine[bg-color="c"] { background: #15f; } + +/* +** Experimental +*/ +BufferViewDock[active=true] { + /* The circle is hardcoded into the title. */ + /* Color only changes on a refresh (F5) (so it's pointless). */ + /* Also colors the border in Breeze. */ + //color: palette(highlight); +} diff --git a/users/tazjin/hanebuschtag.txt b/users/tazjin/hanebuschtag.txt new file mode 100644 index 000000000000..fc5c06522040 --- /dev/null +++ b/users/tazjin/hanebuschtag.txt @@ -0,0 +1,63 @@ +bazurschnaburkini +buchweizengrรผtze +burkischnurkischnurzelwutz +burwurgurken +burwurka +gaschnurzel +gezwurkel +gurzelschnurzelgurke +hanemazurka +hanemazurkelgurkel +haneschlawitzka +haneschnaburkeln +haneschnawurkagurka +haneschnawurkel +haneschnuren +haneschnurkissima +hanewurka +hanewurkini +hanewurzeln +ronzelschlawonzel +ronzelwonzel +schlagurkelwini +schlaraffenwurburzel +schlawiburschnurschlakini +schlawonzel +schlawurkinischnagurka +schlawurzelgegurkel +schlawurzeltrollurzel +schlunzelgarfunzel +schmonzelgafonzel +schmotzrotzel +schnaburka +schnaburkel +schnaburkini +schnackel +schnarkelbarkel +schnarwurzelka +schnawurkeln +schnawurzelgackschnurschnacksschnicks +schnawurzini +schniepel +schnirkelschini +schnรถckel +schnockelgockel +schnorchel +schnรถrk +schnorkelbusch +schnรถrkelknรถrkel +schnorkelorgel +schnรถrks +schnotzelgekrotzel +schnudelwurkini +schnurburka +schnurkini +schnurkinihanfini +schnurzelgawurzel +schnurzelwurzelwutz +schnurzelwutz +strazurkeln +wazurka +wurkelgurkel +wurkelschnurrini +wurzelchakramahurka diff --git a/users/tazjin/homepage/default.nix b/users/tazjin/homepage/default.nix new file mode 100644 index 000000000000..2ce1cf632255 --- /dev/null +++ b/users/tazjin/homepage/default.nix @@ -0,0 +1,77 @@ +# Assembles the website index and configures an nginx instance to +# serve it. +# +# The website is made up of a simple header&footer and content +# elements for things such as blog posts and projects. +# +# Content for the blog is in //users/tazjin/blog instead of here. +{ depot, lib, pkgs, ... }@args: + +with depot; +with nix.yants; + +let + inherit (builtins) readFile replaceStrings sort; + inherit (pkgs) writeFile runCommandNoCC; + + # The different types of entries on the homepage. + entryClass = enum "entryClass" [ "blog" "project" "misc" ]; + + # The definition of a single entry. + entry = struct "entry" { + class = entryClass; + title = string; + url = string; + date = int; # epoch + description = option string; + }; + + escape = replaceStrings [ "<" ">" "&" "'" ] [ "<" ">" "&" "'" ]; + + postToEntry = defun [ web.blog.post entry ] (post: { + class = "blog"; + title = post.title; + url = "/blog/${post.key}"; + date = post.date; + }); + + formatDate = defun [ int string ] (date: readFile (runCommandNoCC "date" {} '' + date --date='@${toString date}' '+%Y-%m-%d' > $out + '')); + + formatEntryDate = defun [ entry string ] (entry: entryClass.match entry.class { + blog = "Blog post from ${formatDate entry.date}"; + project = "Project from ${formatDate entry.date}"; + misc = "Posted on ${formatDate entry.date}"; + }); + + entryToDiv = defun [ entry string ] (entry: '' + <a href="${entry.url}" class="entry ${entry.class}"> + <div> + <p class="entry-title">${escape entry.title}</p> + ${ + lib.optionalString ((entry ? description) && (entry.description != null)) + "<p class=\"entry-description\">${escape entry.description}</p>" + } + <p class="entry-date">${formatEntryDate entry}</p> + </div> + </a> + ''); + + index = entries: pkgs.writeText "index.html" (lib.concatStrings ( + [ (builtins.readFile ./header.html) ] + ++ (map entryToDiv (sort (a: b: a.date > b.date) entries)) + ++ [ (builtins.readFile ./footer.html) ] + )); + + pageEntries = import ./entries.nix; + homepage = index ((map postToEntry users.tazjin.blog.posts) ++ pageEntries); + atomFeed = import ./feed.nix (args // { inherit entry pageEntries; }); +in runCommandNoCC "website" {} '' + mkdir $out + cp ${homepage} $out/index.html + cp ${atomFeed} $out/feed.atom + mkdir $out/static + cp -r ${depot.web.static}/* $out/static + cp -rf ${./static}/* $out/static +'' diff --git a/users/tazjin/homepage/entries.nix b/users/tazjin/homepage/entries.nix new file mode 100644 index 000000000000..1e2b0b03dfc9 --- /dev/null +++ b/users/tazjin/homepage/entries.nix @@ -0,0 +1,74 @@ +[ + { + class = "misc"; + title = "Interview with Joscha Bach"; + url = "https://www.youtube.com/watch?v=P-2P3MSZrBM"; + date = 1594594800; + description = '' + A fascinating, mind-bending interview by Lex Fridman with Joscha + Bach about the Nature of the Universe. + ''; + } + { + class = "misc"; + title = "The Virus Lounge"; + url = "https://tvl.fyi"; + date = 1587435629; + description = "A daily social video call in these trying pandemic times. Join us!"; + } + { + class = "project"; + title = "depot"; + url = "https://code.tvl.fyi/about"; + date = 1576800000; + description = "Merging all of my projects into a single, Nix-based monorepo"; + } + { + class = "project"; + title = "Nixery"; + url = "https://github.com/google/nixery"; + date = 1565132400; + description = "A Nix-backed container registry that builds container images on demand"; + } + { + class = "project"; + title = "kontemplate"; + url = "https://code.tvl.fyi/about/ops/kontemplate"; + date = 1486550940; + description = "Simple file templating tool built for Kubernetes resources"; + } + { + class = "misc"; + title = "dottime"; + url = "https://dotti.me/"; + date = 1560898800; + description = "A universal convention for conveying time (by edef <3)"; + } + { + class = "project"; + title = "journaldriver"; + url = "https://code.tvl.fyi/about/ops/journaldriver"; + date = 1527375600; + description = "Small daemon to forward logs from journald to Stackdriver Logging"; + } + { + class = "misc"; + title = "Principia Discordia"; + url = "https://principiadiscordia.com/book/1.php"; + date = 1495494000; + description = '' + The Principia is a short book I read as a child, and didn't + understand until much later. It shaped much of my world view. + ''; + } + { + class = "misc"; + title = "This Week in Virology"; + url = "http://www.microbe.tv/twiv/"; + date = 1585517557; + description = '' + Podcast with high-quality information about virology, + epidemiology and so on. Highly relevant to COVID19. + ''; + } +] diff --git a/users/tazjin/homepage/feed.nix b/users/tazjin/homepage/feed.nix new file mode 100644 index 000000000000..2a033444e8ba --- /dev/null +++ b/users/tazjin/homepage/feed.nix @@ -0,0 +1,42 @@ +# Creates the Atom feed for my homepage. +{ depot, lib, pkgs, entry, pageEntries, ... }: + +with depot.nix.yants; + +let + inherit (builtins) map readFile; + inherit (lib) max singleton; + inherit (pkgs) writeText; + inherit (depot.web) blog atom-feed; + + pageEntryToEntry = defun [ entry atom-feed.entry ] (e: { + id = "tazjin:${e.class}:${toString e.date}"; + updated = e.date; + published = e.date; + title = e.title; + summary = e.description; + + links = singleton { + rel = "alternate"; + href = e.url; + }; + }); + + allEntries = (with depot.users.tazjin.blog; map (blog.toFeedEntry config) posts) + ++ (map pageEntryToEntry pageEntries); + + feed = { + id = "https://tazj.in/"; + title = "tazjin's interblag"; + subtitle = "my posts, projects and other interesting things"; + rights = "ยฉ 2020 tazjin"; + authors = [ "tazjin" ]; + + links = singleton { + rel = "self"; + href = "https://tazjin/feed.atom"; + }; + + entries = allEntries; + }; +in writeText "feed.atom" (atom-feed.renderFeed feed) diff --git a/users/tazjin/homepage/footer.html b/users/tazjin/homepage/footer.html new file mode 100644 index 000000000000..2f17135066e8 --- /dev/null +++ b/users/tazjin/homepage/footer.html @@ -0,0 +1,2 @@ + </div> +</body> diff --git a/users/tazjin/homepage/header.html b/users/tazjin/homepage/header.html new file mode 100644 index 000000000000..2a5aed4fed57 --- /dev/null +++ b/users/tazjin/homepage/header.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<head><meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="description" content="tazjin's blog"> + <link rel="stylesheet" type="text/css" href="static/tvl.css" media="all"> + <link rel="icon" type="image/webp" href="/static/favicon.webp"> + <link rel="alternate" type="application/atom+xml" href="/feed.atom"> + <title>tazjin's interblag</title> +</head> +<body class="dark"> + <header> + <h1> + <a class="interblag-title" href="/">tazjin's interblag</a> + </h1> + <hr> + </header> + <div class="introduction"> + <p>Hello, illuminated visitor.</p> + <p> + I'm tazjin. Usually you can find + me <a class="dark-link" href="https://git.tazj.in/about">programming computers</a> + using tools such as <a class="dark-link" href="https://nixos.org/nix">Nix</a> + and <a class="dark-link" href="https://www.gnu.org/software/emacs/">Emacs</a>. + </p> + <p> + Below is a collection of + my <span class="project">projects</span>, <span class="blog">blog + posts</span> and some <span class="misc">random things</span> by + me or others. If you'd like to get in touch about anything, send + me a mail at mail@[this domain] or ping me on IRC. + </p> + </div> + <div class="entry-container"> diff --git a/users/tazjin/homepage/static/favicon.webp b/users/tazjin/homepage/static/favicon.webp new file mode 100644 index 000000000000..f99c9085340b --- /dev/null +++ b/users/tazjin/homepage/static/favicon.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/nixery/dominator.webp b/users/tazjin/homepage/static/img/nixery/dominator.webp new file mode 100644 index 000000000000..2d8569a6ca21 --- /dev/null +++ b/users/tazjin/homepage/static/img/nixery/dominator.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/nixery/example_extra.webp b/users/tazjin/homepage/static/img/nixery/example_extra.webp new file mode 100644 index 000000000000..101f0f633aef --- /dev/null +++ b/users/tazjin/homepage/static/img/nixery/example_extra.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/nixery/example_plain.webp b/users/tazjin/homepage/static/img/nixery/example_plain.webp new file mode 100644 index 000000000000..a2b90b3e21d5 --- /dev/null +++ b/users/tazjin/homepage/static/img/nixery/example_plain.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/nixery/ideal_layout.webp b/users/tazjin/homepage/static/img/nixery/ideal_layout.webp new file mode 100644 index 000000000000..0e9f74556682 --- /dev/null +++ b/users/tazjin/homepage/static/img/nixery/ideal_layout.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_1.webp b/users/tazjin/homepage/static/img/watchblob_1.webp new file mode 100644 index 000000000000..27e588e1a145 --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_1.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_2.webp b/users/tazjin/homepage/static/img/watchblob_2.webp new file mode 100644 index 000000000000..b2dea98b4fb4 --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_2.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_3.webp b/users/tazjin/homepage/static/img/watchblob_3.webp new file mode 100644 index 000000000000..99b49373b5b4 --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_3.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_4.webp b/users/tazjin/homepage/static/img/watchblob_4.webp new file mode 100644 index 000000000000..41dbdb6be1cf --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_4.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_5.webp b/users/tazjin/homepage/static/img/watchblob_5.webp new file mode 100644 index 000000000000..c42a4ce1bc0f --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_5.webp Binary files differdiff --git a/users/tazjin/homepage/static/img/watchblob_6.webp b/users/tazjin/homepage/static/img/watchblob_6.webp new file mode 100644 index 000000000000..1440761859dd --- /dev/null +++ b/users/tazjin/homepage/static/img/watchblob_6.webp Binary files differdiff --git a/users/tazjin/keys.nix b/users/tazjin/keys.nix new file mode 100644 index 000000000000..d6941b66d9d8 --- /dev/null +++ b/users/tazjin/keys.nix @@ -0,0 +1,9 @@ +# My SSH public keys +{ ... }: + +let withAll = keys: keys // { all = builtins.attrValues keys; }; +in withAll { + # frog = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKMZzRdcrHTuCPoaFy36MPr5IW/hnImlse/OBOn6udL/ tazjin@frog"; + s10e = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDf7CNlYoauHcSYsMNnCZt5h9QSYH/7keYkg8g3hT32+"; + tverskoy = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBAWvA3RpXpMAqruUbB+eVgvvHCzhs5R9khFRza3YSLeFiIqOxVVgyhzW/BnCSD9t/5JrqRdJIGQLnkQU9m4REhUAAAAEc3NoOg== tazjin@tverskoy"; +} diff --git a/users/tazjin/nisp/transform.el b/users/tazjin/nisp/transform.el new file mode 100644 index 000000000000..89b2bb104d27 --- /dev/null +++ b/users/tazjin/nisp/transform.el @@ -0,0 +1,137 @@ +;; Nix as a Lisp + +(require 'cl-lib) +(require 'json) +(require 's) +(require 'dash) + +(defun nisp/expr (form) + "Entrypoint for Nisp->Nix transformation. Will translate FORM +into Nix code, if it is a valid Nisp expression. + +To make code generation slightly easier, each +expression (including literals) is wrapped in an extra pair of +parens." + (concat + "(" + (pcase form + ;; Special keywords + ('() "null") + (`(let . ,rest) (nisp/let form)) + (`(fn . ,rest) (nisp/fn form)) + (`(if ,cond ,then ,else) (nisp/if cond then else)) + + ;; Nix operators & builtins that need special handling + (`(or ,lhs ,rhs) (nisp/infix "||" lhs rhs)) + (`(and ,lhs ,rhs) (nisp/infix "&&" lhs rhs)) + (`(> ,lhs ,rhs) (nisp/infix ">" lhs rhs)) + (`(< ,lhs ,rhs) (nisp/infix "<" lhs rhs)) + (`(>= ,lhs ,rhs) (nisp/infix ">=" lhs rhs)) + (`(<= ,lhs ,rhs) (nisp/infix "<=" lhs rhs)) + (`(+ ,lhs ,rhs) (nisp/infix "+" lhs rhs)) + (`(- ,lhs ,rhs) (nisp/infix "-" lhs rhs)) + (`(* ,lhs ,rhs) (nisp/infix "*" lhs rhs)) + (`(/ ,lhs ,rhs) (nisp/infix "/" lhs rhs)) + (`(-> ,lhs ,rhs) (nisp/infix "->" lhs rhs)) + (`(? ,lhs ,rhs) (nisp/infix "?" lhs rhs)) + (`(// ,lhs ,rhs) (nisp/infix "//" lhs rhs)) + (`(++ ,lhs ,rhs) (nisp/infix "++" lhs rhs)) + (`(== ,lhs ,rhs) (nisp/infix "==" lhs rhs)) + (`(!= ,lhs ,rhs) (nisp/infix "!=" lhs rhs)) + (`(! ,term) (concat "!" (nisp/expr term))) + (`(- ,term) (concat "-" (nisp/expr term))) + + ;; Attribute sets + (`(attrs . ,rest) (nisp/attribute-set form)) + + ;; Function calls + ((and `(,func . ,args) + (guard (symbolp func))) + (nisp/funcall func args)) + + ;; Primitives + ((pred stringp) (json-encode-string form)) + ((pred numberp) (json-encode-number form)) + ((pred keywordp) (substring (symbol-name form) 1)) + ((pred symbolp) (symbol-name form)) + + ;; Lists + ((pred arrayp) (nisp/list form)) + + (other (error "Encountered unhandled form: %s" other))) + ")")) + +(defun nisp/infix (op lhs rhs) + (concat (nisp/expr lhs) " " op " " (nisp/expr rhs))) + +(defun nisp/funcall (func args) + (concat (symbol-name func) " " (s-join " " (-map #'nisp/expr args)))) + +(defun nisp/let (form) + (pcase form + (`(let . (,bindings . (,body . ()))) (concat "let " + (nisp/let bindings) + (nisp/expr body))) + (`((:inherit . ,inherits) . ,rest) (concat (nisp/inherit (car form)) + " " + (nisp/let rest))) + (`((,name . (,value . ())) .,rest) (concat (symbol-name name) " = " + (nisp/expr value) "; " + (nisp/let rest))) + ('() "in ") + (other (error "malformed form '%s' in let expression" other)))) + +(defun nisp/inherit (form) + (pcase form + (`(:inherit . ,rest) (concat "inherit " (nisp/inherit rest))) + (`((,source) . ,rest) (concat "(" (symbol-name source) ") " (nisp/inherit rest))) + (`(,item . ,rest) (concat (symbol-name item) " " (nisp/inherit rest))) + ('() ";"))) + +(defun nisp/if (cond then else) + (concat "if " (nisp/expr cond) + " then " (nisp/expr then) + " else " (nisp/expr else))) + +(defun nisp/list (form) + (cl-check-type form array) + (concat "[ " + (mapconcat #'nisp/expr form " ") + "]")) + + +(defun nisp/attribute-set (form) + "Attribute sets have spooky special handling because they are +not supported by the reader." + (pcase form + (`(attrs . ,rest) (concat "{ " (nisp/attribute-set rest))) + ((and `(,name . (,value . ,rest)) + (guard (keywordp name))) + (concat (substring (symbol-name name) 1) " = " + (nisp/expr value) "; " + (nisp/attribute-set rest))) + ('() "}"))) + +(defun nisp/fn (form) + (pcase form + (`(fn ,args ,body) (concat + (cl-loop for arg in args + concat (format "%s: " arg)) + (nisp/expr body))))) + +;; The following functions are not part of the transform. + +(defun nisp/eval (form) + (interactive "sExpression: ") + (when (stringp form) + (setq form (read form))) + + (message + ;; TODO(tazjin): Construct argv manually to avoid quoting issues. + (s-chomp + (shell-command-to-string + (concat "nix-instantiate --eval -E '" (nisp/expr form) "'"))))) + +(defun nisp/eval-last-sexp () + (interactive) + (nisp/eval (edebug-last-sexp))) diff --git a/users/tazjin/nittredir/background.js b/users/tazjin/nittredir/background.js new file mode 100644 index 000000000000..8d07f8528520 --- /dev/null +++ b/users/tazjin/nittredir/background.js @@ -0,0 +1,10 @@ +/** + * Register a URL change handler that redirects twitter.com links to nitter.net + */ + +chrome.webRequest.onBeforeRequest.addListener(function(details) { + let url = new URL(details.url); + return { + redirectUrl: ('https://nitter.net' + url.pathname) + }; +}, {urls: ['*://twitter.com/*'], types: ['main_frame']}, ['blocking']); diff --git a/users/tazjin/nittredir/manifest.json b/users/tazjin/nittredir/manifest.json new file mode 100644 index 000000000000..4efe1a6cc245 --- /dev/null +++ b/users/tazjin/nittredir/manifest.json @@ -0,0 +1,15 @@ +{ + "manifest_version": 2, + "name": "nittredir", + "version": "1.0", + "description": "Redirect twitter.com to nitter.net", + "background": { + "scripts": ["background.js"], + "persistent": true + }, + "permissions": [ + "webRequest", + "webRequestBlocking", + "*://twitter.com/*" + ] +} diff --git a/users/tazjin/nix.svg b/users/tazjin/nix.svg new file mode 100644 index 000000000000..4da795a4362b --- /dev/null +++ b/users/tazjin/nix.svg @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns="http://www.w3.org/2000/svg" + width="141.5919mm" + height="122.80626mm" + viewBox="0 0 501.70361 435.14028" + id="svg2" + version="1.1"> + <g transform="translate(-156.33871,933.1905)" visibility="hidden"> + <path + id="lambda-path" + d="m 309.54892,-710.38827 122.19683,211.67512 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4901 -33.22946,-57.8257 z" + /> + <use + id="lambda-1" + href="#lambda-path" + visibility="visible" + transform="rotate(180,407.41868,-715.7565)" + fill="#f8f8ff" /> + <use + id="lambda-2" + visibility="visible" + transform="rotate(-120,407.28823,-715.86995)" + href="#lambda-path" + fill="#0039a6" /> + <use + id="lambda-3" + transform="rotate(-60,407.31177,-715.70016)" + href="#lambda-path" + visibility="visible" + fill="#d52b1e" /> + <use + id="lambda-4" + href="#lambda-path" + visibility="visible" + fill="#f8f8ff" /> + <use + id="lambda-5" + transform="rotate(60,407.11155,-715.78724)" + href="#lambda-path" + visibility="visible" + fill="#0039a6" /> + <use + transform="rotate(120,407.33916,-716.08356)" + id="lambda-6" + href="#lambda-path" + visibility="visible" + fill="#d52b1e" /> + </g> +</svg> diff --git a/users/tazjin/nixos/README.md b/users/tazjin/nixos/README.md new file mode 100644 index 000000000000..662f2a36acac --- /dev/null +++ b/users/tazjin/nixos/README.md @@ -0,0 +1,17 @@ +NixOS configuration +=================== + +My NixOS configurations! It configures most of the packages I require +on my systems, sets up Emacs the way I need and does a bunch of other +interesting things. + +System configuration lives in folders, and some of the modules stem +from `//ops/modules`. + +Machines are deployed with the script at `ops.nixos.rebuild-system`. + +## Configured hosts: + +* `tverskoy` - X13 AMD that's travelling around with me +* `frog` - weapon of mass computation (in storage in London) +* `camden` - NUC formerly serving tazj.in (in storage in London) diff --git a/users/tazjin/nixos/camden/default.nix b/users/tazjin/nixos/camden/default.nix new file mode 100644 index 000000000000..ebd4caac75c7 --- /dev/null +++ b/users/tazjin/nixos/camden/default.nix @@ -0,0 +1,351 @@ +# This file configures camden.tazj.in, my homeserver. +{ depot, pkgs, lib, ... }: + +config: let + nginxRedirect = { from, to, acmeHost }: { + serverName = from; + useACMEHost = acmeHost; + forceSSL = true; + + extraConfig = "return 301 https://${to}$request_uri;"; + }; +in lib.fix(self: { + # Disable the current ACME module and use the old one from 19.09 + # instead, until the various regressions have been sorted out. + # TODO(tazjin): Remove this once the new ACME module works. + disabledModules = [ "security/acme.nix" ]; + imports = + let oldChannel = fetchTarball { + # NixOS 19.09 on 2020-10-04 + url = "https://github.com/NixOS/nixpkgs-channels/archive/75f4ba05c63be3f147bcc2f7bd4ba1f029cedcb1.tar.gz"; + sha256 = "157c64220lf825ll4c0cxsdwg7cxqdx4z559fdp7kpz0g6p8fhhr"; + }; + in [ + "${depot.path}/ops/modules/quassel.nix" + "${depot.path}/ops/modules/smtprelay.nix" + "${oldChannel}/nixos/modules/security/acme.nix" + ]; + + # camden is intended to boot unattended, despite having an encrypted + # root partition. + # + # The below configuration uses an externally connected USB drive + # that contains a LUKS key file to unlock the disk automatically at + # boot. + # + # TODO(tazjin): Configure LUKS unlocking via SSH instead. + boot = { + initrd = { + availableKernelModules = [ + "ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" + "rtsx_usb_sdmmc" "r8169" + ]; + + kernelModules = [ "dm-snapshot" ]; + + luks.devices.camden-crypt = { + fallbackToPassword = true; + device = "/dev/disk/by-label/camden-crypt"; + keyFile = "/dev/sdb"; + keyFileSize = 4096; + }; + }; + + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + + cleanTmpDir = true; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/camden-root"; + fsType = "ext4"; + }; + + "/home" = { + device = "/dev/disk/by-label/camden-home"; + fsType = "ext4"; + }; + + "/boot" = { + device = "/dev/disk/by-label/BOOT"; + fsType = "vfat"; + }; + }; + + nix = { + maxJobs = lib.mkDefault 4; + + trustedUsers = [ "root" "tazjin" ]; + + binaryCaches = [ + "https://tazjin.cachix.org" + ]; + + binaryCachePublicKeys = [ + "tazjin.cachix.org-1:IZkgLeqfOr1kAZjypItHMg1NoBjm4zX9Zzep8oRSh7U=" + ]; + }; + + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + networking = { + hostName = "camden"; + interfaces.enp1s0.useDHCP = true; + interfaces.enp1s0.ipv6.addresses = [ + { + address = "2a01:4b00:821a:ce02::5"; + prefixLength = 64; + } + ]; + + firewall.enable = false; + }; + + time.timeZone = "UTC"; + + # System-wide application setup + programs.fish.enable = true; + programs.mosh.enable = true; + + fonts = { + fonts = [ pkgs.jetbrains-mono ]; + fontconfig.defaultFonts.monospace = [ "JetBrains Mono" ]; + }; + + environment.systemPackages = + # programs from the depot + (with depot; [ + fun.idual.script + fun.idual.setAlarm + ]) ++ + + # programs from nixpkgs + (with pkgs; [ + bat + curl + direnv + emacs27-nox + fswebcam + git + gnupg + google-cloud-sdk + htop + jq + pass + pciutils + restic + ripgrep + screen + ]); + + users = { + # Set up my own user for logging in and doing things ... + users.tazjin = { + isNormalUser = true; + uid = 1000; + extraGroups = [ "git" "wheel" "quassel" "video" ]; + shell = pkgs.fish; + }; + + # Set up a user & group for general git shenanigans + groups.git = {}; + users.git = { + group = "git"; + isSystemUser = true; + }; + }; + + # Services setup + services.openssh.enable = true; + services.haveged.enable = true; + + # Join Tailscale into home network + services.tailscale.enable = true; + + # Allow sudo-ing via the forwarded SSH agent. + security.pam.enableSSHAgentAuth = true; + + # NixOS 20.03 broke nginx and I can't be bothered to debug it + # anymore, all solution attempts have failed, so here's a + # brute-force fix. + systemd.services.fix-nginx = { + script = "${pkgs.coreutils}/bin/chown -R nginx: /var/spool/nginx /var/cache/nginx"; + + serviceConfig = { + User = "root"; + Type = "oneshot"; + }; + }; + + systemd.timers.fix-nginx = { + wantedBy = [ "multi-user.target" ]; + timerConfig = { + OnCalendar = "minutely"; + }; + }; + + # Provision a TLS certificate outside of nginx to avoid + # nixpkgs#38144 + security.acme = { + # acceptTerms = true; + + certs."tazj.in" = { + email = "mail@tazj.in"; + user = "nginx"; + group = "nginx"; + webroot = "/var/lib/acme/acme-challenge"; + extraDomains = { + "cs.tazj.in" = null; + "git.tazj.in" = null; + "www.tazj.in" = null; + + # Local domains (for this machine only) + "camden.tazj.in" = null; + }; + postRun = "systemctl reload nginx"; + }; + + certs."quassel.tazj.in" = { + email = "mail@tazj.in"; + webroot = "/var/lib/acme/challenge-quassel"; + user = "nginx"; # required because of a bug in the ACME module + group = "quassel"; + allowKeysForGroup = true; + }; + }; + + # Forward logs to Google Cloud Platform + services.journaldriver = { + enable = true; + logStream = "home"; + googleCloudProject = "tazjins-infrastructure"; + applicationCredentials = "/etc/gcp/key.json"; + }; + + services.depot.quassel = { + enable = true; + acmeHost = "quassel.tazj.in"; + bindAddresses = [ + "0.0.0.0" + ]; + }; + + services.bitlbee = { + enable = false; + portNumber = 2337; # bees + }; + + # serve my website(s) + services.nginx = { + enable = true; + enableReload = true; + package = with pkgs; nginx.override { + modules = [ nginxModules.rtmp ]; + }; + + recommendedTlsSettings = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + appendConfig = '' + rtmp_auto_push on; + rtmp { + server { + listen 1935; + chunk_size 4000; + + application tvl { + live on; + + allow publish 88.98.195.213; + allow publish 10.0.1.0/24; + deny publish all; + + allow play all; + } + } + } + ''; + + commonHttpConfig = '' + log_format json_combined escape=json + '{' + '"remote_addr":"$remote_addr",' + '"method":"$request_method",' + '"uri":"$request_uri",' + '"status":$status,' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ''; + + virtualHosts.homepage = { + serverName = "tazj.in"; + serverAliases = [ "camden.tazj.in" ]; + default = true; + useACMEHost = "tazj.in"; + root = depot.users.tazjin.homepage; + forceSSL = true; + + extraConfig = '' + ${depot.users.tazjin.blog.oldRedirects} + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + + location ~* \.(webp|woff2)$ { + add_header Cache-Control "public, max-age=31536000"; + } + + location /blog/ { + alias ${depot.users.tazjin.blog.rendered}/; + + if ($request_uri ~ ^/(.*)\.html$) { + return 302 /$1; + } + + try_files $uri $uri.html $uri/ =404; + } + + location = /tazjin { + return 200 "tazjin"; + } + + location /blobs/ { + alias /var/www/blobs/; + } + ''; + }; + + virtualHosts.cgit-old = nginxRedirect { + from = "git.tazj.in"; + to = "code.tvl.fyi"; + acmeHost = "tazj.in"; + }; + + virtualHosts.cs-old = nginxRedirect { + from = "cs.tazj.in"; + to = "cs.tvl.fyi"; + acmeHost = "tazj.in"; + }; + }; + + # Timer units that can be started with systemd-run to set my alarm. + systemd.user.services.light-alarm = { + script = "${depot.fun.idual.script}/bin/idualctl wakey"; + postStart = "${pkgs.systemd}/bin/systemctl --user stop light-alarm.timer"; + serviceConfig = { + Type = "oneshot"; + }; + }; + + system.stateVersion = "19.09"; +}) diff --git a/users/tazjin/nixos/default.nix b/users/tazjin/nixos/default.nix new file mode 100644 index 000000000000..04123a3b5ab1 --- /dev/null +++ b/users/tazjin/nixos/default.nix @@ -0,0 +1,10 @@ +{ depot, lib, ... }: + +let systemFor = sys: (depot.ops.nixos.nixosFor sys).system; +in { + camdenSystem = systemFor depot.users.tazjin.nixos.camden; + frogSystem = systemFor depot.users.tazjin.nixos.frog; + tverskoySystem = systemFor depot.users.tazjin.nixos.tverskoy; + + meta.targets = [ "camdenSystem" "frogSystem" "tverskoySystem" ]; +} diff --git a/users/tazjin/nixos/frog/default.nix b/users/tazjin/nixos/frog/default.nix new file mode 100644 index 000000000000..b3c803c87131 --- /dev/null +++ b/users/tazjin/nixos/frog/default.nix @@ -0,0 +1,285 @@ +{ depot, lib, pkgs, ... }: + +config: let + inherit (pkgs) lieer; + + quasselClient = pkgs.quassel.override { + client = true; + enableDaemon = false; + monolithic = false; + }; +in lib.fix(self: { + imports = [ + "${depot.path}/ops/modules/v4l2loopback.nix" + ]; + + boot = { + tmpOnTmpfs = true; + kernelModules = [ "kvm-amd" ]; + + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + + initrd = { + luks.devices.frog-crypt.device = "/dev/disk/by-label/frog-crypt"; + availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod" ]; + kernelModules = [ "dm-snapshot" ]; + }; + + kernelPackages = pkgs.linuxPackages_latest; + kernel.sysctl = { + "kernel.perf_event_paranoid" = -1; + }; + + # Enable this again if frog is put back into use ... + # + # kernelPatches = [ + # depot.third_party.kernelPatches.trx40_usb_audio + # ]; + }; + + hardware = { + cpu.amd.updateMicrocode = true; + enableRedistributableFirmware = true; + opengl = { + enable = true; + driSupport = true; + driSupport32Bit = true; + }; + + pulseaudio = { + enable = true; + package = pkgs.pulseaudioFull; + }; + + bluetooth = { + enable = true; + }; + }; + + nix = { + maxJobs = 48; + binaryCaches = ["ssh://nix-ssh@whitby.tvl.fyi"]; + binaryCachePublicKeys = ["cache.tvl.fyi:fd+9d1ceCPvDX/xVhcfv8nAa6njEhAGAEe+oGJDEeoc="]; + }; + + networking = { + hostName = "frog"; + useDHCP = true; + + # Don't use ISP's DNS servers: + nameservers = [ + "8.8.8.8" + "8.8.4.4" + ]; + + firewall.enable = false; + }; + + # Generate an immutable /etc/resolv.conf from the nameserver settings + # above (otherwise DHCP overwrites it): + environment.etc."resolv.conf" = with lib; { + source = pkgs.writeText "resolv.conf" '' + ${concatStringsSep "\n" (map (ns: "nameserver ${ns}") self.networking.nameservers)} + options edns0 + ''; + }; + + time.timeZone = "Europe/London"; + + fileSystems = { + "/".device = "/dev/disk/by-label/frog-root"; + "/boot".device = "/dev/disk/by-label/BOOT"; + "/home".device = "/dev/disk/by-label/frog-home"; + }; + + # Configure user account + users.extraUsers.tazjin = { + extraGroups = [ "wheel" "audio" "docker" ]; + isNormalUser = true; + uid = 1000; + shell = pkgs.fish; + }; + + security.sudo = { + enable = true; + extraConfig = "wheel ALL=(ALL:ALL) SETENV: ALL"; + }; + + fonts = { + fonts = with pkgs; [ + corefonts + dejavu_fonts + jetbrains-mono + noto-fonts-cjk + noto-fonts-emoji + ]; + + fontconfig = { + hinting.enable = true; + subpixel.lcdfilter = "light"; + + defaultFonts = { + monospace = [ "JetBrains Mono" ]; + }; + }; + }; + + # Configure location (Vauxhall, London) for services that need it. + location = { + latitude = 51.4819109; + longitude = -0.1252998; + }; + + programs.fish.enable = true; + programs.ssh.startAgent = true; + + services.redshift.enable = true; + services.openssh.enable = true; + services.fstrim.enable = true; + services.blueman.enable = true; + + # Required for Yubikey usage as smartcard + services.pcscd.enable = true; + services.udev.packages = [ + pkgs.yubikey-personalization + ]; + + # Enable Docker for Nixery testing + virtualisation.docker = { + enable = true; + autoPrune.enable = true; + }; + + services.xserver = { + enable = true; + layout = "us"; + xkbOptions = "caps:super"; + exportConfiguration = true; + videoDrivers = [ "amdgpu" ]; + displayManager = { + # Give EXWM permission to control the session. + sessionCommands = "${pkgs.xorg.xhost}/bin/xhost +SI:localuser:$USER"; + + lightdm.enable = true; + lightdm.greeters.gtk.clock-format = "%Hยท%M"; # TODO(tazjin): TZ? + }; + + windowManager.session = lib.singleton { + name = "exwm"; + start = "${depot.users.tazjin.emacs}/bin/tazjins-emacs"; + }; + }; + + # Do not restart the display manager automatically + systemd.services.display-manager.restartIfChanged = lib.mkForce false; + + # clangd needs more than ~2GB in the runtime directory to start up + services.logind.extraConfig = '' + RuntimeDirectorySize=16G + ''; + + # Configure email setup + systemd.user.services.lieer-tazjin = { + description = "Synchronise mail@tazj.in via lieer"; + script = "${lieer}/bin/gmi sync"; + + serviceConfig = { + WorkingDirectory = "%h/mail/account.tazjin"; + Type = "oneshot"; + }; + }; + + systemd.user.timers.lieer-tazjin = { + wantedBy = [ "timers.target" ]; + + timerConfig = { + OnActiveSec = "1"; + OnUnitActiveSec = "180"; + }; + }; + + environment.systemPackages = + # programs from the depot + (with depot; [ + fun.idual.script + fun.uggc + lieer + ops.kontemplate + quasselClient + third_party.git + tools.nsfv-setup + users.tazjin.emacs + ]) ++ + + # programs from nixpkgs + (with pkgs; [ + age + bat + chromium + clang-manpages + clang-tools_11 + clang_11 + curl + direnv + dnsutils + emacs27 # mostly for emacsclient + exa + fd + file + gdb + gnupg + go + google-chrome + google-cloud-sdk + htop + hyperfine + i3lock + iftop + imagemagick + jq + kubectl + linuxPackages.perf + manpages + miller + msmtp + nix-prefetch-github + notmuch + obs-studio + openssh + openssl + pass + pavucontrol + pciutils + pinentry + pinentry-emacs + pmutils + pwgen + ripgrep + rustup + screen + scrot + spotify + tokei + transmission + tree + unzip + usbutils + v4l-utils + vlc + xclip + xsecurelock + yubico-piv-tool + yubikey-personalization + zoxide + + # Commented out because of interim breakage: + # steam + # lutris + ]); + + # ... and other nonsense. + system.stateVersion = "20.03"; +}) diff --git a/users/tazjin/nixos/tverskoy/default.nix b/users/tazjin/nixos/tverskoy/default.nix new file mode 100644 index 000000000000..403e9cd6f27c --- /dev/null +++ b/users/tazjin/nixos/tverskoy/default.nix @@ -0,0 +1,419 @@ +{ depot, lib, pkgs, ... }: + +config: let + quasselClient = pkgs.quassel.override { + client = true; + enableDaemon = false; + monolithic = false; + }; + + # Use a screen lock command that resets the keyboard layout + # before locking, to avoid locking me out when the layout is + # in Russian. + screenLock = pkgs.writeShellScriptBin "tazjin-screen-lock" '' + ${pkgs.xorg.setxkbmap}/bin/setxkbmap us + ${pkgs.xorg.setxkbmap}/bin/setxkbmap -option caps:super + exec ${pkgs.xsecurelock}/bin/xsecurelock + ''; +in lib.fix(self: { + imports = [ + "${depot.third_party.impermanence}/nixos.nix" + "${depot.path + "/ops/modules/automatic-gc.nix"}" + "${pkgs.home-manager.src}/nixos" + ]; + + nix = { + binaryCachePublicKeys = [ + "cache.tvl.su:kjc6KOMupXc1vHVufJUoDUYeLzbwSr9abcAKdn/U1Jk=" + ]; + + binaryCaches = [ + "https://cache.tvl.su" + ]; + }; + + boot = rec { + initrd.availableKernelModules = [ "nvme" "ehci_pci" "xhci_pci" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ]; + initrd.kernelModules = [ ]; + + # Restore /home to the blank snapshot, erasing all ephemeral data. + initrd.postDeviceCommands = lib.mkAfter '' + zfs rollback -r zpool/ephemeral/home@tazjin-clean + ''; + + # Install thinkpad modules for TLP + extraModulePackages = [ kernelPackages.acpi_call ]; + + kernelModules = [ "kvm-amd" "i2c_dev" ]; + kernelPackages = pkgs.linuxPackages_latest; + loader.systemd-boot.enable = true; + loader.efi.canTouchEfiVariables = true; + zfs.enableUnstable = true; + }; + + fileSystems = { + "/" = { + device = "tmpfs"; + fsType = "tmpfs"; + options = [ "defaults" "size=4G" "mode=755" ]; + }; + + "/home" = { + device = "zpool/ephemeral/home"; + fsType = "zfs"; + }; + + "/nix" = { + device = "zpool/local/nix"; + fsType = "zfs"; + }; + + "/depot" = { + device = "zpool/safe/depot"; + fsType = "zfs"; + }; + + "/persist" = { + device = "zpool/safe/persist"; + fsType = "zfs"; + neededForBoot = true; + }; + + # SD card + "/mnt" = { + device = "/dev/disk/by-uuid/c602d703-f1b9-4a44-9e45-94dfe24bdaa8"; + fsType = "ext4"; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/BF4F-388B"; + fsType = "vfat"; + }; + }; + + hardware = { + cpu.amd.updateMicrocode = true; + enableRedistributableFirmware = true; + bluetooth.enable = true; + + opengl = { + enable = true; + extraPackages = with pkgs; [ + vaapiVdpau + libvdpau-va-gl + ]; + }; + }; + + networking = { + hostName = "tverskoy"; + hostId = "3c91827f"; + domain = "tvl.su"; + useDHCP = false; + networkmanager.enable = true; + firewall.enable = false; + + nameservers = [ + "8.8.8.8" + "8.8.4.4" + ]; + }; + + fonts = { + fonts = with pkgs; [ + corefonts + dejavu_fonts + jetbrains-mono + noto-fonts-cjk + noto-fonts-emoji + ]; + + fontconfig = { + hinting.enable = true; + subpixel.lcdfilter = "light"; + + defaultFonts = { + monospace = [ "JetBrains Mono" ]; + }; + }; + }; + + environment.persistence."/persist" = { + directories = [ + "/etc/NetworkManager/system-connections" + "/etc/mullvad-vpn" + "/var/cache/mullvad-vpn" + "/var/lib/bluetooth" + "/var/lib/systemd/coredump" + "/var/log" + ]; + files = [ + "/etc/machine-id" + ]; + }; + + security.rtkit.enable = true; + + services = { + pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; + }; + + redshift.enable = true; + blueman.enable = true; + mullvad-vpn.enable = true; + fwupd.enable = true; + printing.enable = true; + + # expose i2c device as /dev/i2c-amdgpu-dm and make it user-accessible + # this is required for sending control commands to the Dasung screen. + udev.extraRules = '' + SUBSYSTEM=="i2c-dev", ACTION=="add", DEVPATH=="/devices/pci0000:00/0000:00:08.1/0000:06:00.0/i2c-5/i2c-dev/i2c-5", SYMLINK+="i2c-amdgpu-dm", TAG+="uaccess" + ''; + + # Configure TLP to keep battery charge between 40-70% while + # plugged in to the wall (thanks etu for the recommendation). + tlp = { + enable = true; + settings.START_CHARGE_THRESH_BAT0 = 40; + settings.STOP_CHARGE_THRESH_BAT0 = 70; + }; + + xserver = { + enable = true; + layout = "us"; + xkbOptions = "caps:super"; + videoDrivers = [ "amdgpu" ]; + + libinput.enable = true; + + displayManager = { + # Give EXWM permission to control the session. + sessionCommands = "${pkgs.xorg.xhost}/bin/xhost +SI:localuser:$USER"; + lightdm.enable = true; + # lightdm.greeters.gtk.clock-format = "%H:%M"; # TODO(tazjin): TZ? + }; + + windowManager.session = lib.singleton { + name = "exwm"; + start = "${depot.users.tazjin.emacs}/bin/tazjins-emacs"; + }; + }; + + # Automatically collect garbage from the Nix store. + depot.automatic-gc = { + enable = true; + interval = "1 hour"; + diskThreshold = 16; # GiB + maxFreed = 10; # GiB + preserveGenerations = "14d"; + }; + }; + + # Automatically detect location to use for redshift + location.provider = "geoclue2"; + + # Do not restart the display manager automatically + systemd.services.display-manager.restartIfChanged = lib.mkForce false; + + time.timeZone = "Europe/Moscow"; + + users.users.tazjin = { + isNormalUser = true; + createHome = true; + extraGroups = [ "wheel" "networkmanager" "video" "adbusers" ]; + uid = 1000; + shell = pkgs.fish; + initialHashedPassword = "$6$d3FywUNCuZnJ4l.$ZW2ul59MLYon1v1xhC3lTJZfZ91lWW6Tpi13MpME0cJcYZNrsx7ABdgQRn.K05awruG2Y9ARAzURnmiJ31WTS1"; + }; + + programs = { + fish.enable = true; + light.enable = true; + ssh.startAgent = true; + mosh.enable = true; + steam.enable = true; + adb.enable = true; + + # Required by impermanence + fuse.userAllowOther = true; + }; + + environment.systemPackages = + # programs from the depot + (with depot; [ + screenLock + tools.nsfv-setup + users.tazjin.emacs + third_party.agenix.cli + third_party.dfmt + ]) ++ + + # programs from nixpkgs + (with pkgs; [ + amber + bat + chromium + curl + ddcutil + direnv + dmd + dnsutils + emacs27-nox # emacsclient + exa + fd + file + firefox + gdb + gh + git + gnupg + google-chrome + gtk3 # for gtk-launch + htop + hyperfine + iftop + imagemagick + jq + lieer + manpages + mosh + msmtp + mullvad-vpn + networkmanagerapplet + nix-prefetch-github + nmap + notmuch + openssh + openssl + paperlike-go + pass + pavucontrol + pinentry + pinentry-emacs + pulseaudioLight # for pactl + pwgen + quasselClient + rink + ripgrep + rustup + screen + scrot + spotify + syncthing + tig + tokei + tree + unzip + vlc + whois + xsecurelock + zoxide + ]); + + systemd.user.services.lieer-tazjin = { + description = "Synchronise mail@tazj.in via lieer"; + script = "${pkgs.lieer}/bin/gmi sync"; + + serviceConfig = { + WorkingDirectory = "%h/mail/account.tazjin"; + Type = "oneshot"; + }; + }; + + systemd.user.timers.lieer-tazjin = { + wantedBy = [ "timers.target" ]; + + timerConfig = { + OnActiveSec = "1"; + OnUnitActiveSec = "180"; + }; + }; + + home-manager.useGlobalPkgs = true; + home-manager.users.tazjin = { config, lib, ... }: { + imports = [ "${depot.third_party.impermanence}/home-manager.nix" ]; + + home.persistence."/persist/tazjin/home" = { + allowOther = true; + + directories = [ + ".cargo" + ".config/google-chrome" + ".config/quassel-irc.org" + ".config/spotify" + ".config/syncthing" + ".elfeed" + ".gnupg" + ".local/share/Steam" + ".local/share/direnv" + ".local/share/fish" + ".local/share/zoxide" + ".mozilla/firefox" + ".password-store" + ".rustup" + ".ssh" + ".steam" + ".telega" + "go" + "mail" + ]; + + files = [ + ".config/mimeapps.list" + ".notmuch-config" + ]; + }; + + home.activation.screenshots = lib.hm.dag.entryAnywhere '' + $DRY_RUN_CMD mkdir -p $HOME/screenshots + ''; + + programs.git = { + enable = true; + userName = "Vincent Ambo"; + userEmail = "mail@tazj.in"; + extraConfig = { + pull.rebase = true; + init.defaultBranch = "canon"; + }; + }; + + programs.fish = { + enable = true; + interactiveShellInit = '' + ${pkgs.zoxide}/bin/zoxide init fish | source + ''; + }; + + services.screen-locker = { + enable = true; + enableDetectSleep = true; + inactiveInterval = 10; # minutes + lockCmd = "${screenLock}/bin/tazjin-screen-lock"; + }; + + services.picom = { + enable = true; + vSync = true; + backend = "glx"; + }; + + # Enable the dunst notification daemon, but force the + # configuration file separately instead of going via the strange + # Nix->dunstrc encoding route. + services.dunst.enable = true; + xdg.configFile."dunst/dunstrc" = { + source = depot.users.tazjin.dotfiles.dunstrc; + onChange = '' + ${pkgs.procps}/bin/pkill -u "$USER" ''${VERBOSE+-e} dunst || true + ''; + }; + + systemd.user.startServices = true; + }; + + system.stateVersion = "20.09"; +}) diff --git a/users/tazjin/presentations/bootstrapping-2018/README.md b/users/tazjin/presentations/bootstrapping-2018/README.md new file mode 100644 index 000000000000..e9573ae3f2e1 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/README.md @@ -0,0 +1,5 @@ +These are the slides for a talk I gave at the Norwegian Unix User Group on +2018-03-13. + +There is more information and a recording on the [event +page](https://www.nuug.no/aktiviteter/20180313-reproduible-compiler/). diff --git a/users/tazjin/presentations/bootstrapping-2018/default.nix b/users/tazjin/presentations/bootstrapping-2018/default.nix new file mode 100644 index 000000000000..0dff14b2a1a6 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/default.nix @@ -0,0 +1,50 @@ +# This derivation builds the LaTeX presentation. + +{ pkgs, ... }: + +with pkgs; + +let tex = texlive.combine { + inherit (texlive) + beamer + beamertheme-metropolis + etoolbox + euenc + extsizes + fontspec + lualibs + luaotfload + luatex + minted + ms + pgfopts + scheme-basic + translator; +}; +in stdenv.mkDerivation { + name = "nuug-bootstrapping-slides"; + src = ./.; + + FONTCONFIG_FILE = makeFontsConf { + fontDirectories = [ fira fira-code fira-mono ]; + }; + + buildInputs = [ tex fira fira-code fira-mono ]; + buildPhase = '' + # LaTeX needs a cache folder in /home/ ... + mkdir home + export HOME=$PWD/home + # ${tex}/bin/luaotfload-tool -ufv + + # As usual, TeX needs to be run twice ... + function run() { + ${tex}/bin/lualatex presentation.tex + } + run && run + ''; + + installPhase = '' + mkdir -p $out + cp presentation.pdf $out/ + ''; +} diff --git a/users/tazjin/presentations/bootstrapping-2018/drake-meme.png b/users/tazjin/presentations/bootstrapping-2018/drake-meme.png new file mode 100644 index 000000000000..4b036754384f --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/drake-meme.png Binary files differdiff --git a/users/tazjin/presentations/bootstrapping-2018/nixos-logo.png b/users/tazjin/presentations/bootstrapping-2018/nixos-logo.png new file mode 100644 index 000000000000..ce0c98c2cabb --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/nixos-logo.png Binary files differdiff --git a/users/tazjin/presentations/bootstrapping-2018/notes.org b/users/tazjin/presentations/bootstrapping-2018/notes.org new file mode 100644 index 000000000000..363d75352e62 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/notes.org @@ -0,0 +1,89 @@ +#+TITLE: Bootstrapping, reproducibility, etc. +#+AUTHOR: Vincent Ambo +#+DATE: <2018-03-10 Sat> + +* Compiler bootstrapping + This section contains notes about compiler bootstrapping, the + history thereof, which compilers need it - and so on: + +** C + +** Haskell + - self-hosted compiler (GHC) + +** Common Lisp + CL is fairly interesting in this space because it is a language + that is defined via an ANSI standard that compiler implementations + normally actually follow! + + CL has several ecosystem components that focus on making + abstracting away implementation-specific calls and if a self-hosted + compiler is written in CL using those components it can be + cross-bootstrapped. + +** Python + +* A note on runtimes + Sometimes the compiler just isn't enough ... + +** LLVM +** JVM + +* References + https://github.com/mame/quine-relay + https://manishearth.github.io/blog/2016/12/02/reflections-on-rusting-trust/ + https://tests.reproducible-builds.org/debian/reproducible.html + +* Slide thoughts: + 1. Hardware trust has been discussed here a bunch, most recently + during the puri.sm talk. Hardware trust is important, as we see + with IME, but it's striking that people often take a leap to "I'm + now on my trusted Debian with free software". + + Unless you built it yourself from scratch (Spoiler: you haven't) + you're placing trust in what is basically foreign binary blobs. + + Agenda: Implications/attack vectors of this, state of the chicken + & egg, the topic of reproducibility, what can you do? (Nix!) + + 2. Chicken-and-egg issue + + It's an important milestone for a language to become self-hosted: + You begin doing a kind of dogfeeding, you begin to enforce + reliability & consistency guarantees to avoid having to redo your + own codebase constantly and so on. + + However, the implication is now that you need your own compiler + to compile itself. + + Common examples: + - C/C++ compilers needed to build C/C++ compilers: + + GCC 4.7 was the last version of GCC that could be built with a + standard C-compiler, nowadays it is mostly written in C++. + + Certain versions of GCC can be built with LLVM/Clang. + + Clang/LLVM can be compiled by itself and also GCC. + + - Rust was originally written in OCAML but moved to being + self-hosted in 2011. Currently rustc-releases are always built + with a copy of the previous release. + + It's relatively new so we can build the chain all the way. + + Notable exceptions: Some popular languages are not self-hosted, + for example Clojure. Languages also have runtimes, which may be + written in something else (e.g. Haskell -> C runtime) +* How to help: + Most of this advice is about reproducible builds, not bootstrapping, + as that is a much harder project. + + - fix reproducibility issues listed in Debian's issue tracker (focus + on non-Debian specific ones though) + - experiment with NixOS / GuixSD to get a better grasp on the + problem space of reproducibility + + If you want to contribute to bootstrapping, look at + bootstrappable.org and their wiki. Several initiatives such as MES + could need help! diff --git a/users/tazjin/presentations/bootstrapping-2018/presentation.pdf b/users/tazjin/presentations/bootstrapping-2018/presentation.pdf new file mode 100644 index 000000000000..7f435fe5b539 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/presentation.pdf Binary files differdiff --git a/users/tazjin/presentations/bootstrapping-2018/presentation.tex b/users/tazjin/presentations/bootstrapping-2018/presentation.tex new file mode 100644 index 000000000000..d3aa61337554 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/presentation.tex @@ -0,0 +1,251 @@ +\documentclass[12pt]{beamer} +\usetheme{metropolis} +\newenvironment{code}{\ttfamily}{\par} +\title{Where does \textit{your} compiler come from?} +\date{2018-03-13} +\author{Vincent Ambo} +\institute{Norwegian Unix User Group} +\begin{document} + \maketitle + + %% Slide 1: + \section{Introduction} + + %% Slide 2: + \begin{frame}{Chicken and egg} + Self-hosted compilers are often built using themselves, for example: + + \begin{itemize} + \item C-family compilers bootstrap themselves \& each other + \item (Some!) Common Lisp compilers can bootstrap each other + \item \texttt{rustc} bootstraps itself with a previous version + \item ... same for many other languages! + \end{itemize} + \end{frame} + + \begin{frame}{Chicken, egg and ... lizard?} + It's not just compilers: Languages have runtimes, too. + + \begin{itemize} + \item JVM is implemented in C++ + \item Erlang-VM is C + \item Haskell runtime is C + \end{itemize} + + ... we can't ever get away from C, can we? + \end{frame} + + %% Slide 3: + \begin{frame}{Trusting Trust} + \begin{center} + \huge{Could this be exploited?} + \end{center} + \end{frame} + + %% Slide 4: + \begin{frame}{Short interlude: A quine} + \begin{center} + \begin{code} + ((lambda (x) (list x (list 'quote x))) + \newline\vspace*{6mm} '(lambda (x) (list x (list 'quote x)))) + \end{code} + \end{center} + \end{frame} + + %% Slide 5: + \begin{frame}{Short interlude: Quine Relay} + \begin{center} + \includegraphics[ + keepaspectratio=true, + height=\textheight + ]{quine-relay.png} + \end{center} + \end{frame} + + %% Slide 6: + \begin{frame}{Trusting Trust} + An attack described by Ken Thompson in 1983: + + \begin{enumerate} + \item Modify a compiler to detect when it's compiling itself. + \item Let the modification insert \textit{itself} into the new compiler. + \item Add arbitrary attack code to the modification. + \item \textit{Optional!} Remove the attack from the source after compilation. + \end{enumerate} + \end{frame} + + %% Slide 7: + \begin{frame}{Damage potential?} + \begin{center} + \large{Let your imagination run wild!} + \end{center} + \end{frame} + + %% Slide 8: + \section{Countermeasures} + + %% Slide 9: + \begin{frame}{Diverse Double-Compiling} + Assume we have: + + \begin{itemize} + \item Target language compilers $A$ and $T$ + \item The source code of $A$: $ S_{A} $ + \end{itemize} + \end{frame} + + %% Slide 10: + \begin{frame}{Diverse Double-Compiling} + Apply the first stage (functional equivalence): + + \begin{itemize} + \item $ X = A(S_{A})$ + \item $ Y = T(S_{A})$ + \end{itemize} + + Apply the second stage (bit-for-bit equivalence): + + \begin{itemize} + \item $ V = X(S_{A})$ + \item $ W = Y(S_{A})$ + \end{itemize} + + Now we have a new problem: Reproducibility! + \end{frame} + + %% Slide 11: + \begin{frame}{Reproducibility} + Bit-for-bit equivalent output is hard, for example: + + \begin{itemize} + \item Timestamps in output artifacts + \item Non-deterministic linking order in concurrent builds + \item Non-deterministic VM \& memory states in outputs + \item Randomness in builds (sic!) + \end{itemize} + \end{frame} + + \begin{frame}{Reproducibility} + \begin{center} + Without reproducibility, we can never trust that any shipped + binary matches the source code! + \end{center} + \end{frame} + + %% Slide 12: + \section{(Partial) State of the Union} + + \begin{frame}{The Desired State} + \begin{center} + \begin{enumerate} + \item Full-source bootstrap! + \item All packages reproducible! + \end{enumerate} + \end{center} + \end{frame} + + %% Slide 13: + \begin{frame}{Bootstrapping Debian} + \begin{itemize} + \item Sparse information on the Debian-wiki + \item Bootstrapping discussions mostly resolve around new architectures + \item GCC is compiled by depending on previous versions of GCC + \end{itemize} + \end{frame} + + \begin{frame}{Reproducing Debian} + Debian has a very active effort for reproducible builds: + + \begin{itemize} + \item Organised information about reproducibility status + \item Over 90\% reproducibility in Debian package base! + \end{itemize} + \end{frame} + + \begin{frame}{Short interlude: Nix} + \begin{center} + \includegraphics[ + keepaspectratio=true, + height=0.7\textheight + ]{nixos-logo.png} + \end{center} + \end{frame} + + \begin{frame}{Short interlude: Nix} + \begin{center} + \includegraphics[ + keepaspectratio=true, + height=0.90\textheight + ]{drake-meme.png} + \end{center} + \end{frame} + + \begin{frame}{Short interlude: Nix} + \begin{center} + \includegraphics[ + keepaspectratio=true, + height=0.7\textheight + ]{nixos-logo.png} + \end{center} + \end{frame} + + \begin{frame}{Bootstrapping NixOS} + Nix evaluation can not recurse forever: The bootstrap can not + simply depend on a previous GCC. + + Workaround: \texttt{bootstrap-tools} tarball from a previous + binary cache is fetched and used. + + An unfortunate magic binary blob ... + \end{frame} + + \begin{frame}{Reproducing NixOS} + Not all reproducibility patches have been ported from Debian. + + However: Builds are fully repeatable via the Nix fundamentals! + \end{frame} + + \section{Future Developments} + + \begin{frame}{Bootstrappable: stage0} + Hand-rolled ``Cthulhu's Path to Madness'' hex-programs: + + \begin{itemize} + \item No non-auditable binary blobs + \item Aims for understandability by 70\% of programmers + \item End goal is a full-source bootstrap of GCC + \end{itemize} + \end{frame} + + + \begin{frame}{Bootstrappable: MES} + Bootstrapping the ``Maxwell Equations of Software'': + + \begin{itemize} + \item Minimal C-compiler written in Scheme + \item Minimal Scheme-interpreter (currently in C, but intended to + be rewritten in stage0 macros) + \item End goal is full-source bootstrap of the entire GuixSD + \end{itemize} + \end{frame} + + \begin{frame}{Other platforms} + \begin{itemize} + \item Nix for Darwin is actively maintained + \item F-Droid Android repository works towards fully reproducible + builds of (open) Android software + \item Mobile devices (phones, tablets, etc.) are a lost cause at + the moment + \end{itemize} + \end{frame} + + \begin{frame}{Thanks!} + Resources: + \begin{itemize} + \item bootstrappable.org + \item reproducible-builds.org + \end{itemize} + + @tazjin | mail@tazj.in + \end{frame} +\end{document} diff --git a/users/tazjin/presentations/bootstrapping-2018/quine-relay.png b/users/tazjin/presentations/bootstrapping-2018/quine-relay.png new file mode 100644 index 000000000000..5644dc3900e3 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/quine-relay.png Binary files differdiff --git a/users/tazjin/presentations/bootstrapping-2018/result.pdfpc b/users/tazjin/presentations/bootstrapping-2018/result.pdfpc new file mode 100644 index 000000000000..b0fa6c9a0ef8 --- /dev/null +++ b/users/tazjin/presentations/bootstrapping-2018/result.pdfpc @@ -0,0 +1,142 @@ +[file] +result +[last_saved_slide] +10 +[font_size] +20000 +[notes] +### 1 +- previous discussions of hardware trust (e.g. purism presentation) +- people leap to "now I'm on my trusted Debian!" +- unless you built it from scratch (spoiler: you haven't) you're *trusting* someone + +Agenda: Implications of trust with focus on bootstrap paths and reproducibility, plus how you can help.### 2 +self-hosting: +- C-family: GCC pre/post 4.7, Clang +- Common Lisp: Sunshine land! (with SBCL) +- rustc: Bootstrap based on previous versions (C++ transpiler underway!) +- many other languages also work this way! + +(Noteable counterexample: Clojure is written in Java!)### 3 + +- compilers are just one bit, the various runtimes exist, too!### 4 + +Could this be exploited? + +People don't think about where their compiler comes from. + +Even if they do, they may only go so far as to say "I'll just recompile it using <other compiler>". + +Unfortunately, spoiler alert, life isn't that easy in the computer world and yes, exploitation is possible.### 5 + +- describe what a quine is +- classic Lisp quine +- explain demo quine +- demo demo quine + +- this is interesting, but not useful - can quines do more than that?### 6 + +- quine-relay: "art project" with 128-language circular quine + +- show source of quine-relay + +- (demo quine relay?) + +- side-note: this program is very, very trustworthy!### 7 + +Ken Thompson (designer of UNIX and a couple other things!) received Turing award in 1983, and described attack in speech. + +- figure out how to detect self-compilation +- make that modification a quine +- insert modification into new compiler +- add attack code to modification +- remove attack from source, distributed binary will still be compromised! it's like evolution :)### 8 + +damage potential is basically infinite: + +- classic "login" attack +=> also applicable to other credentials + +- attack (weaken) crypto algorithms + +- you can probably think of more!### 10 + +idea being: potential vulnerability would have to work across compilers: + +the more compilers we can introduce (e.g. more architectures, different versions, different compilers), the harder it gets for a vulnerability to survive all of those + +The more compilers, the merrier! Lisps are pretty good at this.### 11 + +if we get a bit-mismatch after DDC, not all hope is lost: Maybe the thing just isn't reproducible! + +- many reasons for failures +- timestamps are a classic! artifacts can be build logs, metadata in ZIP-files or whatever +- non-determinism is the devil +- sometimes people actively introduce build-randomness (NaCl)### 12 + +- Does that binary download on the project's website really match the source? + +- Your Linux packages are signed by someone - cool - but what does that mean?### 13 + +Two things should be achieved - gross oversimplification - to get to the ideal "desired state of the union": + +1. full-source bootstrap: without ever introducing any binaries, go from nothing to a full Linux distribution + +2. when packages are distributed, we should be able to know the expected output of a source package beforehand + +=> suddenly binary distributions become a cache! But more on Nix later.### 14 + +- Debian project does not seem as concerned with bootstrapping as with reproducibility +- Debian mostly bootstraps on new architectures (using cross-compilation and similar techniques, from an existing binary base) +- core bootstrap (GCC & friends) is performed with previous Debian version and depending on GCC### 15 + +... however! Debian cares about reproducibility. + +- automated testing of reproducibility +- information about the status of all packages is made available in repos +- Over 90% packages of packages are reproducible! + +< show reproducible builds website > + +Debian is still fundamentally a binary distribution though, but it doesn't have to be that way.### 16 + +Nix - a purely functional package manager + +It's not a new project (10+ years), been discussed here before, has multiple components: package manager, language, NixOS. + +Instead of describing *how* to build a thing, Nix describes *what* to build:### 17 +### 19 + +In Nix, it's impossible to say "GCC is the result of applying GCC to the GCC source", because that happens to be infinite recursion. + +Bootstrapping in Nix works by introducing a binary pinned by its full-hash, which was built on some previous Nix version. + +Unfortunately also just a magic binary blob ... ### 20 + +NixOS is not actively porting all of Debian's reproducibility patches, but builds are fully repeatable: + +- introducing a malicious compiler would produce a different input hash -> different package + +Future slide: hope is not lost! Things are underway.### 21 + +- bootstrappable.org (demo?) is an umbrella page for several projects working on bootstrappability + +- stage0 is an important piece: manually, small, auditable Hex programs to get to a Hex macro expander + +- end goal is a full-source bootrap, but pieces are missing### 22 + +MES is out of the GuixSD circles (explain Guix, GNU Hurd joke) + +- idea being that once you have a Lisp, you have all of computing (as Alan Key said) + +- includes MesCC in Scheme -> can *almost* make a working tinyCC -> can *almost* make a working gcc 4.7 + +- minimal Scheme interpreter, currently built in C to get the higher-level stuff to work, goal is rewrite in hex +- bootstrapping Guix is the end goal### 23 + +- userspace in Darwin has a Nix project +- unsure about other BSDs, but if anyone knows - input welcome! +- F-Droid has reproducible Android packages, but that's also userspace only +- All other mobile platforms are a lost cause + +Generally, all closed-source software is impossible to trust. diff --git a/users/tazjin/presentations/erlang-2016/.skip-subtree b/users/tazjin/presentations/erlang-2016/.skip-subtree new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/.skip-subtree diff --git a/users/tazjin/presentations/erlang-2016/README.md b/users/tazjin/presentations/erlang-2016/README.md new file mode 100644 index 000000000000..e1b6c83b99cc --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/README.md @@ -0,0 +1,6 @@ +These are the slides for a presentation I gave for the Oslo javaBin meetup in +2016. + +Unfortunately there is no recording of the presentation due to a technical error +(video was recorded, but no audio). This is a bit of a shame because I think +these are some of the best slides I've ever made. diff --git a/users/tazjin/presentations/erlang-2016/presentation.md b/users/tazjin/presentations/erlang-2016/presentation.md new file mode 100644 index 000000000000..526564b88268 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/presentation.md @@ -0,0 +1,222 @@ +slidenumbers: true +Erlang. +====== + +### Fault-tolerant, concurrent programming. + +--- + +## A brief history of Erlang + +--- + +![](https://www.ericsson.com/thinkingahead/the-networked-society-blog/wp-content/uploads/2014/09/bfW5FSr.jpg) + + +^ Telefontornet in Stockholm, around 1890. Used until 1913. + +--- + +![](https://3.bp.blogspot.com/-UF7W9yTUO2g/VBqw-1HNTzI/AAAAAAAAPeg/KvsMbNSAcII/s1600/6835942484_1531372d8f_b.jpg) + +^ Telephones were operated manually at Switchboards. Anyone old enough to remember? I'm certainly not. + +--- + +![fit](https://russcam.github.io/fsharp-akka-talk/images/ericsson-301-AXD.png) + +^ Eventually we did that in software, and we got better at it over time. Ericsson AXD 301, first commercial Erlang switch. But lets take a step back. + +--- + +## Phone switches must be ... + +Highly concurrent + +Fault-tolerant + +Distributed + +(Fast!) + +![right 150%](http://learnyousomeerlang.com/static/img/erlang-the-movie.png) + +--- + +## ... and so is Erlang! + +--- + +## Erlang as a whole: + +- Unique process model (actors!) +- Built-in fault-tolerance & error handling +- Distributed processes +- Three parts! + +--- + +## Part 1: Erlang, the language + +- Functional +- Prolog-inspired syntax +- Everything is immutable +- *Extreme* pattern-matching + +--- +### Hello Joe + +```erlang +hello_joe. +``` + +--- +### Hello Joe + +```erlang +-module(hello1). +-export([hello_joe/0]). + +hello_joe() -> + hello_joe. +``` + +--- +### Hello Joe + +```erlang +-module(hello1). +-export([hello_joe/0]). + +hello_joe() -> + hello_joe. + +% 1> c(hello1). +% {ok,hello1} +% 2> hello1:hello_joe(). +% hello_joe +``` + +--- +### Hello Joe + +```erlang +-module(hello2). +-export([hello/1]). + +hello(Name) -> + io:format("Hello ~s!~n", [Name]). + +% 3> c(hello2). +% {ok,hello2} +% 4> hello2:hello("Joe"). +% Hello Joe! +% ok +``` + +--- + +## [fit] Hello ~~world~~ Joe is boring! +## [fit] Lets do it with processes. + +--- +### Hello Server + +```erlang +-module(hello_server). +-export([start_server/0]). + +start_server() -> + spawn(fun() -> server() end). + +server() -> + receive + {greet, Name} -> + io:format("Hello ~s!~n", [Name]), + server() + end. +``` + +--- + +## [fit] Some issues with that ... + +- What about unused messages? +- What if the server crashes? + +--- + +## [fit] Part 2: Open Telecom Platform + +### **It's called Erlang/OTP for a reason.** + +--- + +# OTP: An Application Framework + +- Supervision - keep processes alive! + +- OTP Behaviours - common process patterns + +- Extensive standard library + +- Error handling, debuggers, testing, ... + +- Lots more! + +^ Standard library includes lots of things from simple network libraries over testing frameworks to cryptography, complete LDAP clients etc. + +--- + +# Supervision + +![inline](http://erlang.org/doc/design_principles/sup6.gif) + +^ Supervision keeps processes alive, different restart behaviours, everything should be supervised to avoid "process" (and therefore memory) leaks + +--- + +# OTP Behaviours + +* `gen_server` +* `gen_statem` +* `gen_event` +* `supervisor` + +^ gen = generic. explain server, explain statem, event = event handling with registered handlers, supervisor ... + +--- + +`gen_server` + +--- + +## [fit] Part 3: BEAM + +### Bogdan/Bjรธrn Erlang Abstract machine + +--- + +## A VM for Erlang + +* Many were written, BEAM survived +* Concurrent garbage-collection +* Lower-level bytecode than JVM +* Very open to new languages + (Elixir, LFE, Joxa, ...) + +--- + +## What next? + +* Ole's talk, obviously! +* Learn You Some Erlang! + www.learnyousomeerlang.com +* Watch *Erlang the Movie* +* (soon!) Join the Oslo BEAM meetup group + +--- + +# [fit] Questions? + +`@tazjin` diff --git a/users/tazjin/presentations/erlang-2016/presentation.pdf b/users/tazjin/presentations/erlang-2016/presentation.pdf new file mode 100644 index 000000000000..ec8d996704b2 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/presentation.pdf Binary files differdiff --git a/users/tazjin/presentations/erlang-2016/src/hello.erl b/users/tazjin/presentations/erlang-2016/src/hello.erl new file mode 100644 index 000000000000..56404a0c5a20 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello.erl @@ -0,0 +1,5 @@ +-module(hello). +-export([hello_joe/0]). + +hello_joe() -> + hello_joe. diff --git a/users/tazjin/presentations/erlang-2016/src/hello1.erl b/users/tazjin/presentations/erlang-2016/src/hello1.erl new file mode 100644 index 000000000000..ca78261399e1 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello1.erl @@ -0,0 +1,5 @@ +-module(hello1). +-export([hello_joe/0]). + +hello_joe() -> + hello_joe. diff --git a/users/tazjin/presentations/erlang-2016/src/hello2.erl b/users/tazjin/presentations/erlang-2016/src/hello2.erl new file mode 100644 index 000000000000..2d1f6c84c401 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello2.erl @@ -0,0 +1,11 @@ +-module(hello2). +-export([hello/1]). + +hello(Name) -> + io:format("Hey ~s!~n", [Name]). + +% 3> c(hello2). +% {ok,hello2} +% 4> hello2:hello("Joe"). +% Hello Joe! +% ok diff --git a/users/tazjin/presentations/erlang-2016/src/hello_server.erl b/users/tazjin/presentations/erlang-2016/src/hello_server.erl new file mode 100644 index 000000000000..01df14ac57d5 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello_server.erl @@ -0,0 +1,12 @@ +-module(hello_server). +-export([start_server/0, server/0]). + +start_server() -> + spawn(fun() -> server() end). + +server() -> + receive + {greet, Name} -> + io:format("Hello ~s!~n", [Name]), + hello_server:server() + end. diff --git a/users/tazjin/presentations/erlang-2016/src/hello_server2.erl b/users/tazjin/presentations/erlang-2016/src/hello_server2.erl new file mode 100644 index 000000000000..24bb934ee503 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello_server2.erl @@ -0,0 +1,36 @@ +-module(hello_server2). +-behaviour(gen_server). +-compile(export_all). + +%%% Start callback for supervisor +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%%% gen_server callbacks + +init([]) -> + {ok, sets:new()}. + +handle_call({greet, Name}, _From, State) -> + io:format("Hello ~s!~n", [Name]), + NewState = sets:add_element(Name, State), + {reply, ok, NewState}; + +handle_call({bye, Name}, _From, State) -> + io:format("Goodbye ~s!~n", [Name]), + NewState = sets:del_element(Name, State), + {reply, ok, NewState}. + +terminate(normal, State) -> + [io:format("Goodbye ~s!~n", [Name]) || Name <- State], + ok. + +%%% Unused gen_server callbacks +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +handle_cast(_Request, State) -> + {noreply, State}. diff --git a/users/tazjin/presentations/erlang-2016/src/hello_sup.erl b/users/tazjin/presentations/erlang-2016/src/hello_sup.erl new file mode 100644 index 000000000000..7fee0928c575 --- /dev/null +++ b/users/tazjin/presentations/erlang-2016/src/hello_sup.erl @@ -0,0 +1,24 @@ +-module(hello_sup). +-behaviour(supervisor). +-export([start_link/0, init/1]). + +%%% Module API + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%% Supervisor callbacks + +init([]) -> + Children = [hello_spec()], + {ok, { {one_for_one, 5, 10}, Children}}. + +%%% Private + +hello_spec() -> + #{id => hello_server2, + start => {hello_server2, start_link, []}, + restart => permanent, + shutdown => 5000, + type => worker, + module => [hello_server2]}. diff --git a/users/tazjin/presentations/servant-2016/Makefile b/users/tazjin/presentations/servant-2016/Makefile new file mode 100644 index 000000000000..96115ec2cbfc --- /dev/null +++ b/users/tazjin/presentations/servant-2016/Makefile @@ -0,0 +1,8 @@ +all: slides + +slides: + lualatex --shell-escape slides.tex + +clean: + rm -f slides.aux slides.log slides.nav \ + slides.out slides.toc slides.snm diff --git a/users/tazjin/presentations/servant-2016/README.md b/users/tazjin/presentations/servant-2016/README.md new file mode 100644 index 000000000000..8cfb04a42417 --- /dev/null +++ b/users/tazjin/presentations/servant-2016/README.md @@ -0,0 +1,7 @@ +These are the slides for my presentation about [servant][] at [Oslo Haskell][]. + +A full video recording of the presentation is available [on Vimeo][]. + +[servant]: https://haskell-servant.github.io/ +[Oslo Haskell]: http://www.meetup.com/Oslo-Haskell/events/227107530/ +[on Vimeo]: https://vimeo.com/153901805 diff --git a/users/tazjin/presentations/servant-2016/slides.pdf b/users/tazjin/presentations/servant-2016/slides.pdf new file mode 100644 index 000000000000..842a667e1bcc --- /dev/null +++ b/users/tazjin/presentations/servant-2016/slides.pdf Binary files differdiff --git a/users/tazjin/presentations/servant-2016/slides.pdfpc b/users/tazjin/presentations/servant-2016/slides.pdfpc new file mode 100644 index 000000000000..ed46003768c0 --- /dev/null +++ b/users/tazjin/presentations/servant-2016/slides.pdfpc @@ -0,0 +1,75 @@ +[file] +slides.pdf +[font_size] +10897 +[notes] +### 1 +13### 2 +Let's talk about servant, which is several things: +API description DSL, we'll speak about how this DSL works +and why it's at the type level + +Interpretations of the types resulting from that DSL, for example in +web servers or API clients + +Servant is commonly used or implementing services with APIs, or for accessing +other APIs with a simple, typed client +### 3 +Why type-level DSLs? +Type-level DSL: express *something*, e.g. endpoints of API, on type level by combining types. Types can be uninhabited + +Phil Wadler's: expression problem: things should be extensible both in the cases of a type, and in the functions operating on the type +Normal data types: can't add new constructors easily +Servant lifts thisup to simply allow the declaration of new types that can be included in the DSL, and new interpretations that can be attached to the types through typeclasses + +APIs become first-class citizens, can pass them around, combine them etc, they are separate from interpretations such as server implementations. In contrast, in most webframeworks, API declaration is implicit + +(Mention previous attemps at type-safe web, Yesod / web-routes + boomerang etc) +### 4 +Three extensions are necessary: +TypeOperators lets us use infix operators on the type level as constructors +DataKinds promotes new type declarations to the kind level, makes type-level literals (strings and natural numbers) available, lets us use type-level lists and pairs in combination with typeoperators +TypeFamilies: Type-level functions, map one set of types to another, come in two forms (type families, non-injective; data families, injective), more powerful than associated types +### 5 +Here you can see servant's general syntax, we define an API type as a simple alias of some other type combinations +strings are type-level strings, not actually values, represent path elements +endpoints are separated by :<|>, all endpoints end in a method with content types and return types +Capture captures path segments, but there are other combinators, for example for headers +Everything that is used from the request is expressed in types, enforcing checkability, no "escape hatch" inside handlers to get request +Every combinator has associated interpretations through typeclasses +### 6 +Explain type alias, point out Capture +Server is a type level function (type family), as mentioned earlier +### 7 +If we expand server (in ghci with kind!) we can see the actual type of the +function +### 8 +Lets speak about some interpretations of these things +### 9 +Servant server is the main interpretation that people are interested in, it's used +for taking a type specification and creating a server from it +Based on WAI, the web application interface, common abstraction for web servers which came out of the Yesod project. Implemented by the web server warp, which Yesod runs on +### 10 +Explain snippet, path gets removed from server type (irrelevant for handler), +route extracts string to value level +### 11 +Explain echo server quickly +### 12 +servant client allows generation of Haskell functions that query the API with the same types +this makes for easy to use RPC for example +### 13 +A lot of other interpretations exist for all kinds of things, mock servers for testing, foreign functions in various languages, documentation ... +### 14 +Demo! +1. Go quickly through code +2. Run server, query with curl +3. Open javascript function +4. Show JS code in the thing +5. Open the map itself +6. Open GHCi, use client +7. Generate docs +### 15 +Conclusion +Servant is pretty good, it's very easy to get started and it's great to raise the level of things that the compiler can tell you about when you do them wrong. +### 16 +Drawbacks. diff --git a/users/tazjin/presentations/servant-2016/slides.tex b/users/tazjin/presentations/servant-2016/slides.tex new file mode 100644 index 000000000000..d5947eb9421a --- /dev/null +++ b/users/tazjin/presentations/servant-2016/slides.tex @@ -0,0 +1,137 @@ +\documentclass[12pt]{beamer} +\usetheme{metropolis} +\usepackage{minted} + +\newenvironment{code}{\ttfamily}{\par} + +\title{servant} +\subtitle{Defining web APIs at the type-level} + +\begin{document} +\metroset{titleformat frame=smallcaps} +\setminted{fontsize=\scriptsize} + + +\maketitle + +\section{Introduction} + +\begin{frame}{Type-level DSLs?} + \begin{itemize} + \item (Uninhabited) types with attached ``meaning'' + \item The Expression Problem (Wadler 1998) + \item API representation and interpretation are separated + \item APIs become first-class citizens + \end{itemize} +\end{frame} + +\begin{frame}{Haskell extensions} + \begin{itemize} + \item TypeOperators + \item DataKinds + \item TypeFamilies + \end{itemize} +\end{frame} + +\begin{frame}[fragile]{A servant example} + \begin{minted}{haskell} + type PubAPI = "pubs" :> Get โ[JSON] [Pub] + :<|> "pubs" :> "tagged" + :> Capture "tag" Text + :> Get โ[JSON] [Pub] + \end{minted} +\end{frame} + +\begin{frame}[fragile]{Computed types} + \begin{minted}{haskell} + type TaggedPubs = "tagged" :> Capture "tag" Text :> ... + + taggedPubsHandler :: Server TaggedPubs + taggedPubsHandler tag = ... + \end{minted} +\end{frame} + +\begin{frame}[fragile]{Computed types} + \begin{minted}{haskell} + type TaggedPubs = "tagged" :> Capture "tag" Text :> ... + + taggedPubsHandler :: Server TaggedPubs + taggedPubsHandler tag = ... + + Server TaggedPubs ~ + Text -> EitherT ServantErr IO [Pub] + \end{minted} +\end{frame} + +\section{Interpretations} + +\begin{frame}{servant-server} + The one everyone is interested in! + + \begin{itemize} + \item Based on WAI, can run on warp + \item Interprets combinators with a simple \texttt{HasServer c} class + \item Easy to use! + \end{itemize} +\end{frame} + +\begin{frame}[fragile]{HasServer ...} + \begin{minted}{haskell} + instance (KnownSymbol path, HasServer sublayout) + => HasServer (path :> sublayout) where + type ServerT (path :> sublayout) m = ServerT sublayout m + + route ... + where + pathString = symbolVal (Proxy :: Proxy path) + \end{minted} +\end{frame} + +\begin{frame}[fragile]{Server example} + \begin{minted}{haskell} + type Echo = Capture "echo" Text :> Get โ[PlainText] Text + + echoAPI :: Proxy Echo + echoAPI = Proxy + + echoServer :: Server Echo + echoServer = return + \end{minted} +\end{frame} + +\begin{frame}{servant-client} + \begin{itemize} + \item Generates Haskell client functions for API + \item Same types as API specification: For RPC the whole ``web layer'' is abstracted away + \item Also easy to use! + \end{itemize} +\end{frame} + +\begin{frame}{servant-docs, servant-js ...} + Many other interpretations exist already, for example: + \begin{itemize} + \item Documentation generation + \item Foreign function export (e.g. Elm, JavaScript) + \item Mock-server generation + \end{itemize} +\end{frame} + +\section{Demo} + +\section{Conclusion} + +\begin{frame}{Drawbacks} + \begin{itemize} + \item Haskell has no custom open kinds (yet) + \item Proxies are ugly + \item Errors can be a bit daunting + \end{itemize} +\end{frame} + +\begin{frame}{Questions?} + รlkartet: github.com/tazjin/pubkartet \\ + Slides: github.com/tazjin/servant-presentation + + @tazjin +\end{frame} +\end{document} diff --git a/users/tazjin/presentations/systemd-2016/.gitignore b/users/tazjin/presentations/systemd-2016/.gitignore new file mode 100644 index 000000000000..1a38620fe9cc --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/.gitignore @@ -0,0 +1,6 @@ +slides.aux +slides.log +slides.nav +slides.out +slides.snm +slides.toc diff --git a/users/tazjin/presentations/systemd-2016/.skip-subtree b/users/tazjin/presentations/systemd-2016/.skip-subtree new file mode 100644 index 000000000000..108b3507ddd1 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/.skip-subtree @@ -0,0 +1 @@ +No Nix files will ever be under this tree ... diff --git a/users/tazjin/presentations/systemd-2016/Makefile b/users/tazjin/presentations/systemd-2016/Makefile new file mode 100644 index 000000000000..ac5dde3cb32f --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/Makefile @@ -0,0 +1,11 @@ +all: slides.pdf + +slides.toc: + lualatex slides.tex + +slides.pdf: slides.toc + lualatex slides.tex + +clean: + rm -f slides.aux slides.log slides.nav \ + slides.out slides.toc slides.snm diff --git a/users/tazjin/presentations/systemd-2016/README.md b/users/tazjin/presentations/systemd-2016/README.md new file mode 100644 index 000000000000..7f004b7d14ca --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/README.md @@ -0,0 +1,6 @@ +This repository contains the slides for my systemd presentation at Hackeriet. + +Requires LaTeX, [beamer][] and the [metropolis][] theme. + +[beamer]: http://mirror.hmc.edu/ctan/macros/latex/contrib/beamer/ +[metropolis]: https://github.com/matze/mtheme diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-error.service b/users/tazjin/presentations/systemd-2016/demo/demo-error.service new file mode 100644 index 000000000000..b2d4c9d34799 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-error.service @@ -0,0 +1,7 @@ +[Unit] +Description=Demonstrate failing units +OnFailure=demo-notify@%n.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/false diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-limits.slice b/users/tazjin/presentations/systemd-2016/demo/demo-limits.slice new file mode 100644 index 000000000000..998185d26177 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-limits.slice @@ -0,0 +1,7 @@ +[Unit] +Description=Limited resources demo +DefaultDependencies=no +Before=slices.target + +[Slice] +CPUQuota=10% diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-notify@.service b/users/tazjin/presentations/systemd-2016/demo/demo-notify@.service new file mode 100644 index 000000000000..e25524b4e230 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-notify@.service @@ -0,0 +1,6 @@ +[Unit] +Description=Demonstrate systemd templating by sending a notification + +[Service] +Type=oneshot +ExecStart=/usr/bin/notify-send 'Systemd notification' '%i' diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-path.path b/users/tazjin/presentations/systemd-2016/demo/demo-path.path new file mode 100644 index 000000000000..87f1342da995 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-path.path @@ -0,0 +1,6 @@ +[Unit] +Description=Demonstrate systemd path units + +[Path] +DirectoryNotEmpty=/tmp/hackeriet +Unit=demo.service diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-stress.service b/users/tazjin/presentations/systemd-2016/demo/demo-stress.service new file mode 100644 index 000000000000..7e14f13e29d9 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-stress.service @@ -0,0 +1,6 @@ +[Unit] +Description=Stress test CPU + +[Service] +Slice=demo.slice +ExecStart=/usr/bin/stress -c 5 diff --git a/users/tazjin/presentations/systemd-2016/demo/demo-timer.timer b/users/tazjin/presentations/systemd-2016/demo/demo-timer.timer new file mode 100644 index 000000000000..34eccb98b02a --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo-timer.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Demonstrate systemd timers + +[Timer] +OnActiveSec=2 +OnUnitActiveSec=5 +AccuracySec=5 +Unit=demo.service +# OnCalendar=Thu,Fri 2016-*-1,5 11:12:13 + +[Install] +WantedBy=multi-user.target diff --git a/users/tazjin/presentations/systemd-2016/demo/demo.service b/users/tazjin/presentations/systemd-2016/demo/demo.service new file mode 100644 index 000000000000..fcc710ad933f --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/demo.service @@ -0,0 +1,6 @@ +[Unit] +Description=Demo unit for systemd + +[Service] +Type=oneshot +ExecStart=/usr/bin/echo "Systemd unit activated. Hello Hackeriet." diff --git a/users/tazjin/presentations/systemd-2016/demo/notes.md b/users/tazjin/presentations/systemd-2016/demo/notes.md new file mode 100644 index 000000000000..b4866b1642bb --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/demo/notes.md @@ -0,0 +1,27 @@ +# simple oneshot + +Run `demo-notify@hello.service` + +# simple timer + +Run `demo-timer.timer`, show both + +# enabling + +Enable `demo-timer.timer`, go to symlink folder, disable + +# OnError + +Show & run `demo-error.service` + +# cgroups demo + +Start `demo-stress.service` without, show in htop, stop +Show slice unit, start slice unit +Add Slice=demo-limits.slice +daemon-reload +Start stress again + +# Proper service + +Look at nginx unit diff --git a/users/tazjin/presentations/systemd-2016/slides.pdf b/users/tazjin/presentations/systemd-2016/slides.pdf new file mode 100644 index 000000000000..384db2a6e0af --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/slides.pdf Binary files differdiff --git a/users/tazjin/presentations/systemd-2016/slides.pdfpc b/users/tazjin/presentations/systemd-2016/slides.pdfpc new file mode 100644 index 000000000000..99326bd8bf4e --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/slides.pdfpc @@ -0,0 +1,85 @@ +[file] +slides.pdf +[notes] +### 1 +### 2 +Let's start off by looking at what an init system is, how they used to work and what systemd does different before we go into more systemd-specific details. +### 3 +system processes that are started include for example FS mounts, network settings, powertop... +system services are long-running processes such as daemons, e.g. SSH, database or web servers, session managers, udev ... + +orphans: Process whose parent has finished somehow, gets adopted by init system +-> when a process terminates its parent must call wait() to get its exit() code, if there is no init system adopting orphans the process would become a zombie +### 4 +Before systemd there were simple init systems that just did the tasks listed on the previous slide. +Init scripts -> increased greatly in complexity over time, look at incomprehensible skeleton for Debian service init scripts +Runlevels -> things such as single-user mode, full multiuser mode, reboot, halt + +Init will run all the scripts, but it will not do much more than print information on success/failure of started scripts + +Init scripts run strictly sequential + +Init is unaware of inter-service dependencies, expressed through prefixing scripts with numbers etc. + +Init will not watch processes after system is booted -> crashing daemons will not automatically restart +### 5 +### 6 +How systemd came to be + +Considering the lack of process monitoring, problematic things about init scripts -> legacy init systems have drawbacks + +Apple had already built launchd, a more featured init system that monitored running processes, could automatically restart them and allowed for certain advanced features -> however it is awful to use and wrap your head around + +Lennart Poettering of Pulseaudio fame and Kay Sievers decided to implement a new init system to address these problems, while taking certain clues from Apple's design +### 7 +Systemd's design goals +### 8 +No more init scripts with opaque effects -> services are clearly defined units +Unit dependencies -> systemd can figure out what can be started in parallel +Process supervision: Unit can be configured in many ways, e.g. always restart, only restart on success etc +Service logs: We'll talk more about this later +### 9 +Units are the core component of systemd that users deal with. They define services and everything else that systemd needs to start and manage. +Note that all these are the names of the respective man page on a system with systemd installed +Types: +systemd.service - processes controlled by systemd +systemd.target - equivalent to "runlevels", grouping of units for synchronisation +systemd.timer - more powerful replacement of cron that starts other units +systemd.path - systemd equvialent of inotify, watches files/folders -> launches units +systemd.socket - expose local IPC or network sockets, launch units on connections +systemd.device - trigger units when certain devices are connected +systemd.mount - systemd equivalent of fstab entries +systemd.swap - like mount +systemd.slice - unit groups for resource management purposes +... and a few more specialised ones +### 10 +Linux cgroups are a new resource management feature added quite a long time ago, but not used much. +Cgroups can be created manually and processes can be moved into them in order to control resource utilisation +Few people used them before systemd, limits.conf was often much easier but not as fine-grained +Systemd changed this +### 11 +Systemd collects standard output and stderr from all processes into its journal system +they provide a tool for querying the log, for example grouping service logs together with correct timestamps, querying, +### 12 +Systemd tooling, most important one is systemctl for general service management +journalctl is the query and management tool for journald +systemd-analyze is used for figuring out performance issues, for example by analysing the boot process, can make cool graphs of dependencies +systemd-cgtop is like top, but not on a process level - it's on a cgroup/slice level, shows combined usage of cgroups +systemd-cgls lists contents of systemd's cgroups to see which services are in what group +there also exist a bunch of others that we'll skip for now +### 13 +### 14 +### 15 +Systemd criticism comes from many directions and usually focuses on a few points +feature-creep: systemd is absorbing a lot of different services +### 16 +explain diagram a bit +### 17 +opaque: as a result, systemd has a lot more internal complexity that people can't easily wrap your mind around. However I argue that unless you're using something like suckless' sinit with your own scripts, you probably have no idea what your init does today anyways +unstable: this was definitely true even in the first stable release, with the binary log format getting corrupted for example. I haven't personally experienced any trouble with it recently though. +Another thing is that services start depending on systemd when they shouldn't, a problem for the BSD world (who cares (hey christoph!)) +### 18 +Despite criticism, systemd was adopted rapidly by large portions of the Linux +Initially in RedHat, because Poettering and co work there and it was clear from the beginning that it would be there +ArchLinux (which I'm using) and a few others followed suit quite quickly +Eventually, the big Debian init system discussion - after a lot of flaming - led to Debian adopting it as well, which had a ripple effect for related distros such as Ubuntu which abandoned upstart for it. \ No newline at end of file diff --git a/users/tazjin/presentations/systemd-2016/slides.tex b/users/tazjin/presentations/systemd-2016/slides.tex new file mode 100644 index 000000000000..c613cefd7ec4 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/slides.tex @@ -0,0 +1,160 @@ +\documentclass[12pt]{beamer} +\usetheme{metropolis} + +\newenvironment{code}{\ttfamily}{\par} + +\title{systemd} +\subtitle{The standard Linux init system} + +\begin{document} +\metroset{titleformat frame=smallcaps} + +\maketitle + +\section{Introduction} + +\begin{frame}{What is an init system?} + An init system is the first userspace process (PID 1) started in a UNIX-like system. It handles: + + \begin{itemize} + \item Starting system processes and services to prepare the environment + \item Adopting and ``reaping'' orphaned processes + \end{itemize} +\end{frame} + +\begin{frame}{Classical init systems} + Init systems before systemd - such as SysVinit - were very simple. + + \begin{itemize} + \item Services and processes to run are organised into ``init scripts'' + \item Scripts are linked to specific runlevels + \item Init system is configured to boot into a runlevel + \end{itemize} + +\end{frame} + +\section{systemd} + +\begin{frame}{Can we do better?} + \begin{itemize} + \item ``legacy'' init systems have a lot of drawbacks + \item Apple is taking a different approach on OS X + \item Systemd project was founded to address these issues + \end{itemize} +\end{frame} + +\begin{frame}{Systemd design goals} + \begin{itemize} + \item Expressing service dependencies + \item Monitoring service status + \item Enable parallel service startups + \item Ease of use + \end{itemize} +\end{frame} + +\begin{frame}{Systemd - the basics} + \begin{itemize} + \item No scripts are executed, only declarative units + \item Units have explicit dependencies + \item Processes are supervised + \item cgroups are utilised to apply resource limits + \item Service logs are managed and centrally queryable + \item Much more! + \end{itemize} +\end{frame} + +\begin{frame}{Systemd units} + Units specify how and what to start. Several types exist: + \begin{code} + \small + \begin{columns}[T,onlytextwidth] + \column{0.5\textwidth} + \begin{itemize} + \item systemd.service + \item systemd.target + \item systemd.timer + \item systemd.path + \item systemd.socket + \end{itemize} + \column{0.5\textwidth} + \begin{itemize} + \item systemd.device + \item systemd.mount + \item systemd.swap + \item systemd.slice + \end{itemize} + \end{columns} + \end{code} +\end{frame} + + +\begin{frame}{Resource management} + Systemd utilises Linux \texttt{cgroups} for resource management, specifically CPU, disk I/O and memory usage. + + \begin{itemize} + \item Hierarchical setup of groups makes it easy to limit resources for a set of services + \item Units can be attached to a \texttt{systemd.slice} for controlling resources for a group of services + \item Resource limits can also be specified directly in the unit + \end{itemize} +\end{frame} + +\begin{frame}{journald} + Systemd comes with an integrated log management solution, replacing software such as \texttt{syslog-ng}. + \begin{itemize} + \item All process output is collected in the journal + \item \texttt{journalctl} tool provides many options for querying and tailing logs + \item Children of processes automatically log to the journal as well + \item \textbf{Caveat:} Hard to learn initially + \end{itemize} +\end{frame} + +\begin{frame}{Systemd tooling} + A variety of CLI-tools exist for managing systemd systems. + \begin{code} + \begin{itemize} + \item systemctl + \item journalctl + \item systemd-analyze + \item systemd-cgtop + \item systemd-cgls + \end{itemize} + \end{code} + + Let's look at some of them. +\end{frame} + +\section{Demo} + +\section{Controversies} + +\begin{frame}{Systemd criticism} + Systemd has been heavily criticised, usually focusing around a few points: + \begin{itemize} + \item Feature-creep: Systemd absorbs more and more other services + \end{itemize} +\end{frame} + +\begin{frame}{Systemd criticism} + \includegraphics[keepaspectratio=true,width=\textwidth]{systemdcomponents.png} +\end{frame} + +\begin{frame}{Systemd criticism} + Systemd has been heavily criticised, usually focusing around a few points: + \begin{itemize} + \item Feature-creep: Systemd absorbs more and more other services + \item Opaque: systemd's inner workings are harder to understand than old \texttt{init} + \item Unstable: development is quick and breakage happens + \end{itemize} +\end{frame} + +\begin{frame}{Systemd adoption} + Systemd was initially adopted by RedHat (and related distributions). + + It spread quickly to others, for example ArchLinux. + + Debian and Ubuntu were the last major players who decided to adopt it, but not without drama. +\end{frame} + +\section{Questions?} + +\end{document} diff --git a/users/tazjin/presentations/systemd-2016/systemdcomponents.png b/users/tazjin/presentations/systemd-2016/systemdcomponents.png new file mode 100644 index 000000000000..a22c762f7e13 --- /dev/null +++ b/users/tazjin/presentations/systemd-2016/systemdcomponents.png Binary files differdiff --git a/users/tazjin/rlox/.gitignore b/users/tazjin/rlox/.gitignore new file mode 100644 index 000000000000..29e65519ba35 --- /dev/null +++ b/users/tazjin/rlox/.gitignore @@ -0,0 +1,3 @@ +result +/target +**/*.rs.bk diff --git a/users/tazjin/rlox/Cargo.lock b/users/tazjin/rlox/Cargo.lock new file mode 100644 index 000000000000..d8107726e067 --- /dev/null +++ b/users/tazjin/rlox/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "rlox" +version = "0.1.0" + diff --git a/users/tazjin/rlox/Cargo.toml b/users/tazjin/rlox/Cargo.toml new file mode 100644 index 000000000000..b66af6ba85d3 --- /dev/null +++ b/users/tazjin/rlox/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rlox" +version = "0.1.0" +authors = ["Vincent Ambo <mail@tazj.in>"] +edition = "2018" + +[features] +# Enables debugging/disassembling in the bytecode interpreter. Off by +# default as it is quite spammy. +disassemble = [] diff --git a/users/tazjin/rlox/README.md b/users/tazjin/rlox/README.md new file mode 100644 index 000000000000..1d2692d09cc1 --- /dev/null +++ b/users/tazjin/rlox/README.md @@ -0,0 +1,7 @@ +This is an interpreter for the Lox language, based on the book "[Crafting +Interpreters](https://craftinginterpreters.com/)". + +The book's original code uses Java, but I don't want to use Java, so I've +decided to take on the extra complexity of porting it to Rust. + +Note: This implements the first of two Lox interpreters. diff --git a/users/tazjin/rlox/default.nix b/users/tazjin/rlox/default.nix new file mode 100644 index 000000000000..e50ac32be452 --- /dev/null +++ b/users/tazjin/rlox/default.nix @@ -0,0 +1,5 @@ +{ depot, ... }: + +depot.third_party.naersk.buildPackage { + src = ./.; +} diff --git a/users/tazjin/rlox/examples/builtins.lox b/users/tazjin/rlox/examples/builtins.lox new file mode 100644 index 000000000000..39af1d73c4c9 --- /dev/null +++ b/users/tazjin/rlox/examples/builtins.lox @@ -0,0 +1 @@ +print clock(); diff --git a/users/tazjin/rlox/examples/fib.lox b/users/tazjin/rlox/examples/fib.lox new file mode 100644 index 000000000000..1b91e9db94ac --- /dev/null +++ b/users/tazjin/rlox/examples/fib.lox @@ -0,0 +1,6 @@ +fun fib(n) { + if (n <= 1) return n; + return fib(n - 2) + fib(n - 1); +} + +print fib(30); diff --git a/users/tazjin/rlox/examples/func.lox b/users/tazjin/rlox/examples/func.lox new file mode 100644 index 000000000000..d197ad11383f --- /dev/null +++ b/users/tazjin/rlox/examples/func.lox @@ -0,0 +1,5 @@ +fun foo(name) { + print("hello " + name); +} + +foo("bar"); diff --git a/users/tazjin/rlox/examples/hello.lox b/users/tazjin/rlox/examples/hello.lox new file mode 100644 index 000000000000..31752d9e2f5e --- /dev/null +++ b/users/tazjin/rlox/examples/hello.lox @@ -0,0 +1,34 @@ +var a = 12; +var b = a * 2; + +{ + var b = a * 3; + a = 42; + print b; +} + +print a; +print b; + +if (5 > 4) + print "it's true"; +else + print "it's false"; + +if (false) + print "it's not true"; + +if (true and false) + print "won't happen"; + +if (true or false) + print "will happen"; + +var n = 5; +while (n > 0) { + print "counting down"; + n = n - 1; +} + +for(var i = 0; i < 10; i = i + 1) + print "bla"; diff --git a/users/tazjin/rlox/examples/if.lox b/users/tazjin/rlox/examples/if.lox new file mode 100644 index 000000000000..5f335c0e8b29 --- /dev/null +++ b/users/tazjin/rlox/examples/if.lox @@ -0,0 +1,7 @@ +if (false) { + print "yes"; +} else { + print "no"; +} + +print "afterwards"; diff --git a/users/tazjin/rlox/examples/scope.lox b/users/tazjin/rlox/examples/scope.lox new file mode 100644 index 000000000000..d563807943ba --- /dev/null +++ b/users/tazjin/rlox/examples/scope.lox @@ -0,0 +1,19 @@ +var a = "global a"; +var b = "global b"; +var c = "global c"; +{ + var a = "outer a"; + var b = "outer b"; + { + var a = "inner a"; + print a; + print b; + print c; + } + print a; + print b; + print c; +} +print a; +print b; +print c; diff --git a/users/tazjin/rlox/examples/scope2.lox b/users/tazjin/rlox/examples/scope2.lox new file mode 100644 index 000000000000..f826c8658803 --- /dev/null +++ b/users/tazjin/rlox/examples/scope2.lox @@ -0,0 +1,10 @@ +var a = "global"; +{ + fun showA() { + print a; + } + + showA(); + var a = "block"; + showA(); +} diff --git a/users/tazjin/rlox/examples/slow.lox b/users/tazjin/rlox/examples/slow.lox new file mode 100644 index 000000000000..dd6fb5e4bf4d --- /dev/null +++ b/users/tazjin/rlox/examples/slow.lox @@ -0,0 +1,9 @@ +fun fib(n) { + if (n < 2) return n; + return fib(n - 1) + fib(n - 2); +} + +var before = clock(); +print fib(40); +var after = clock(); +print after - before; diff --git a/users/tazjin/rlox/examples/var.lox b/users/tazjin/rlox/examples/var.lox new file mode 100644 index 000000000000..7af90b3f0bee --- /dev/null +++ b/users/tazjin/rlox/examples/var.lox @@ -0,0 +1,8 @@ +var a = 10; +var b = 5; + +{ + var b = 10; + var c = 2; + a * b * c; +} diff --git a/users/tazjin/rlox/rustfmt.toml b/users/tazjin/rlox/rustfmt.toml new file mode 100644 index 000000000000..df99c69198f5 --- /dev/null +++ b/users/tazjin/rlox/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/users/tazjin/rlox/src/bytecode/chunk.rs b/users/tazjin/rlox/src/bytecode/chunk.rs new file mode 100644 index 000000000000..fc5cd34fdf4f --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/chunk.rs @@ -0,0 +1,93 @@ +use std::ops::Index; + +use super::opcode::{CodeIdx, ConstantIdx, OpCode}; +use super::value; + +// In the book, this type is a hand-rolled dynamic array +// implementation in C. The main benefit of following that approach +// would be avoiding issues with OpCode variants not having equal +// sizes, but for the purpose of this I'm going to ignore that +// problem. +#[derive(Debug, Default)] +pub struct Chunk { + pub code: Vec<OpCode>, + lines: Vec<Span>, + constants: Vec<value::Value>, +} + +#[derive(Debug)] +struct Span { + /// Source code line + line: usize, + + /// Number of instructions derived from this line + count: usize, +} + +impl Chunk { + pub fn add_op(&mut self, data: OpCode, line: usize) -> CodeIdx { + let idx = self.code.len(); + self.code.push(data); + self.add_line(line); + CodeIdx(idx) + } + + pub fn add_constant(&mut self, data: value::Value) -> usize { + let idx = self.constants.len(); + self.constants.push(data); + idx + } + + pub fn constant(&self, idx: ConstantIdx) -> &value::Value { + self.constants.index(idx.0) + } + + fn add_line(&mut self, line: usize) { + match self.lines.last_mut() { + Some(span) if span.line == line => span.count += 1, + _ => self.lines.push(Span { line, count: 1 }), + } + } + + pub fn get_line(&self, offset: usize) -> usize { + let mut pos = 0; + for span in &self.lines { + pos += span.count; + if pos > offset { + return span.line; + } + } + + panic!("invalid chunk state: line missing for offset {}", offset); + } +} + +// Disassembler + +/// Print a single disassembled instruction at the specified offset. +/// Some instructions are printed "raw", others have special handling. +#[cfg(feature = "disassemble")] +pub fn disassemble_instruction(chunk: &Chunk, offset: usize) { + print!("{:04} ", offset); + + let line = chunk.get_line(offset); + if offset > 0 && line == chunk.get_line(offset - 1) { + print!(" | "); + } else { + print!("{:4} ", line); + } + + match chunk.code.index(offset) { + OpCode::OpConstant(idx) => { + println!("OpConstant({:?}) '{:?}'", idx, chunk.constant(*idx)) + } + op => println!("{:?}", op), + } +} + +#[cfg(feature = "disassemble")] +pub fn disassemble_chunk(chunk: &Chunk) { + for (idx, _) in chunk.code.iter().enumerate() { + disassemble_instruction(chunk, idx); + } +} diff --git a/users/tazjin/rlox/src/bytecode/compiler.rs b/users/tazjin/rlox/src/bytecode/compiler.rs new file mode 100644 index 000000000000..3e8a80653f91 --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/compiler.rs @@ -0,0 +1,737 @@ +use super::chunk::Chunk; +use super::errors::{Error, ErrorKind, LoxResult}; +use super::interner::{InternedStr, Interner}; +use super::opcode::{CodeIdx, CodeOffset, ConstantIdx, OpCode, StackIdx}; +use super::value::Value; +use crate::scanner::{self, Token, TokenKind}; + +#[cfg(feature = "disassemble")] +use super::chunk; + +#[derive(Debug)] +enum Depth { + Unitialised, + At(usize), +} + +impl Depth { + fn above(&self, theirs: usize) -> bool { + match self { + Depth::Unitialised => false, + Depth::At(ours) => *ours > theirs, + } + } + + fn below(&self, theirs: usize) -> bool { + match self { + Depth::Unitialised => false, + Depth::At(ours) => *ours < theirs, + } + } +} + +#[derive(Debug)] +struct Local { + name: Token, + depth: Depth, +} + +#[derive(Debug, Default)] +struct Locals { + locals: Vec<Local>, + scope_depth: usize, +} + +struct Compiler<T: Iterator<Item = Token>> { + tokens: T, + chunk: Chunk, + panic: bool, + errors: Vec<Error>, + strings: Interner, + locals: Locals, + + current: Option<Token>, + previous: Option<Token>, +} + +#[derive(Debug, PartialEq, PartialOrd)] +enum Precedence { + None, + Assignment, // = + Or, // or + And, // and + Equality, // == != + Comparison, // < > <= >= + Term, // + - + Factor, // * / + Unary, // ! - + Call, // . () + Primary, +} + +type ParseFn<T> = fn(&mut Compiler<T>) -> LoxResult<()>; + +struct ParseRule<T: Iterator<Item = Token>> { + prefix: Option<ParseFn<T>>, + infix: Option<ParseFn<T>>, + precedence: Precedence, +} + +impl<T: Iterator<Item = Token>> ParseRule<T> { + fn new( + prefix: Option<ParseFn<T>>, + infix: Option<ParseFn<T>>, + precedence: Precedence, + ) -> Self { + ParseRule { + prefix, + infix, + precedence, + } + } +} + +impl Precedence { + // Return the next highest precedence, if there is one. + fn next(&self) -> Self { + match self { + Precedence::None => Precedence::Assignment, + Precedence::Assignment => Precedence::Or, + Precedence::Or => Precedence::And, + Precedence::And => Precedence::Equality, + Precedence::Equality => Precedence::Comparison, + Precedence::Comparison => Precedence::Term, + Precedence::Term => Precedence::Factor, + Precedence::Factor => Precedence::Unary, + Precedence::Unary => Precedence::Call, + Precedence::Call => Precedence::Primary, + Precedence::Primary => panic!( + "invalid parser state: no higher precedence than Primary" + ), + } + } +} + +fn rule_for<T: Iterator<Item = Token>>(token: &TokenKind) -> ParseRule<T> { + match token { + TokenKind::LeftParen => { + ParseRule::new(Some(Compiler::grouping), None, Precedence::None) + } + + TokenKind::Minus => ParseRule::new( + Some(Compiler::unary), + Some(Compiler::binary), + Precedence::Term, + ), + + TokenKind::Plus => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Term) + } + + TokenKind::Slash => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Factor) + } + + TokenKind::Star => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Factor) + } + + TokenKind::Number(_) => { + ParseRule::new(Some(Compiler::number), None, Precedence::None) + } + + TokenKind::True => { + ParseRule::new(Some(Compiler::literal), None, Precedence::None) + } + + TokenKind::False => { + ParseRule::new(Some(Compiler::literal), None, Precedence::None) + } + + TokenKind::Nil => { + ParseRule::new(Some(Compiler::literal), None, Precedence::None) + } + + TokenKind::Bang => { + ParseRule::new(Some(Compiler::unary), None, Precedence::None) + } + + TokenKind::BangEqual => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Equality) + } + + TokenKind::EqualEqual => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Equality) + } + + TokenKind::Greater => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Comparison) + } + + TokenKind::GreaterEqual => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Comparison) + } + + TokenKind::Less => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Comparison) + } + + TokenKind::LessEqual => { + ParseRule::new(None, Some(Compiler::binary), Precedence::Comparison) + } + + TokenKind::Identifier(_) => { + ParseRule::new(Some(Compiler::variable), None, Precedence::None) + } + + TokenKind::String(_) => { + ParseRule::new(Some(Compiler::string), None, Precedence::None) + } + + _ => ParseRule::new(None, None, Precedence::None), + } +} + +macro_rules! consume { + ( $self:ident, $expected:pat, $err:expr ) => { + match $self.current().kind { + $expected => $self.advance(), + _ => $self.error_at($self.current().line, $err), + } + }; +} + +impl<T: Iterator<Item = Token>> Compiler<T> { + fn compile(&mut self) -> LoxResult<()> { + self.advance(); + + while !self.match_token(&TokenKind::Eof) { + self.declaration()?; + } + + self.end_compiler() + } + + fn advance(&mut self) { + self.previous = self.current.take(); + self.current = self.tokens.next(); + } + + fn expression(&mut self) -> LoxResult<()> { + self.parse_precedence(Precedence::Assignment) + } + + fn var_declaration(&mut self) -> LoxResult<()> { + let idx = self.parse_variable()?; + + if self.match_token(&TokenKind::Equal) { + self.expression()?; + } else { + self.emit_op(OpCode::OpNil); + } + + self.expect_semicolon("expect ';' after variable declaration")?; + self.define_variable(idx) + } + + fn define_variable(&mut self, var: Option<ConstantIdx>) -> LoxResult<()> { + if self.locals.scope_depth == 0 { + self.emit_op(OpCode::OpDefineGlobal( + var.expect("should be global"), + )); + } else { + self.locals + .locals + .last_mut() + .expect("fatal: variable not yet added at definition") + .depth = Depth::At(self.locals.scope_depth); + } + + Ok(()) + } + + fn declaration(&mut self) -> LoxResult<()> { + if self.match_token(&TokenKind::Var) { + self.var_declaration()?; + } else { + self.statement()?; + } + + if self.panic { + self.synchronise(); + } + + Ok(()) + } + + fn statement(&mut self) -> LoxResult<()> { + if self.match_token(&TokenKind::Print) { + self.print_statement() + } else if self.match_token(&TokenKind::If) { + self.if_statement() + } else if self.match_token(&TokenKind::LeftBrace) { + self.begin_scope(); + self.block()?; + self.end_scope(); + Ok(()) + } else { + self.expression_statement() + } + } + + fn print_statement(&mut self) -> LoxResult<()> { + self.expression()?; + self.expect_semicolon("expect ';' after print statement")?; + self.emit_op(OpCode::OpPrint); + Ok(()) + } + + fn begin_scope(&mut self) { + self.locals.scope_depth += 1; + } + + fn end_scope(&mut self) { + debug_assert!(self.locals.scope_depth > 0, "tried to end global scope"); + self.locals.scope_depth -= 1; + + while self.locals.locals.len() > 0 + && self.locals.locals[self.locals.locals.len() - 1] + .depth + .above(self.locals.scope_depth) + { + self.emit_op(OpCode::OpPop); + self.locals.locals.remove(self.locals.locals.len() - 1); + } + } + + fn block(&mut self) -> LoxResult<()> { + while !self.check(&TokenKind::RightBrace) + && !self.check(&TokenKind::Eof) + { + self.declaration()?; + } + + consume!( + self, + TokenKind::RightBrace, + ErrorKind::ExpectedToken("Expected '}' after block.") + ); + Ok(()) + } + + fn expression_statement(&mut self) -> LoxResult<()> { + self.expression()?; + self.expect_semicolon("expect ';' after expression")?; + // TODO(tazjin): Why did I add this originally? + // self.emit_op(OpCode::OpPop); + Ok(()) + } + + fn if_statement(&mut self) -> LoxResult<()> { + consume!( + self, + TokenKind::LeftParen, + ErrorKind::ExpectedToken("Expected '(' after 'if'") + ); + + self.expression()?; + + consume!( + self, + TokenKind::RightParen, + ErrorKind::ExpectedToken("Expected ')' after condition") + ); + + let then_jump = self.emit_op(OpCode::OpJumpPlaceholder(false)); + self.emit_op(OpCode::OpPop); + self.statement()?; + let else_jump = self.emit_op(OpCode::OpJumpPlaceholder(true)); + self.patch_jump(then_jump); + self.emit_op(OpCode::OpPop); + + if self.match_token(&TokenKind::Else) { + self.statement()?; + } + + self.patch_jump(else_jump); + + Ok(()) + } + + fn number(&mut self) -> LoxResult<()> { + if let TokenKind::Number(num) = self.previous().kind { + self.emit_constant(Value::Number(num), true); + return Ok(()); + } + + unreachable!("internal parser error: entered number() incorrectly") + } + + fn grouping(&mut self) -> LoxResult<()> { + self.expression()?; + consume!( + self, + TokenKind::RightParen, + ErrorKind::ExpectedToken("Expected ')' after expression") + ); + Ok(()) + } + + fn unary(&mut self) -> LoxResult<()> { + // TODO(tazjin): Avoid clone + let kind = self.previous().kind.clone(); + + // Compile the operand + self.parse_precedence(Precedence::Unary)?; + + // Emit operator instruction + match kind { + TokenKind::Bang => self.emit_op(OpCode::OpNot), + TokenKind::Minus => self.emit_op(OpCode::OpNegate), + _ => unreachable!("only called for unary operator tokens"), + }; + + Ok(()) + } + + fn binary(&mut self) -> LoxResult<()> { + // Remember the operator + let operator = self.previous().kind.clone(); + + // Compile the right operand + let rule: ParseRule<T> = rule_for(&operator); + self.parse_precedence(rule.precedence.next())?; + + // Emit operator instruction + match operator { + TokenKind::Minus => self.emit_op(OpCode::OpSubtract), + TokenKind::Plus => self.emit_op(OpCode::OpAdd), + TokenKind::Star => self.emit_op(OpCode::OpMultiply), + TokenKind::Slash => self.emit_op(OpCode::OpDivide), + + TokenKind::BangEqual => { + self.emit_op(OpCode::OpEqual); + self.emit_op(OpCode::OpNot) + } + + TokenKind::EqualEqual => self.emit_op(OpCode::OpEqual), + TokenKind::Greater => self.emit_op(OpCode::OpGreater), + + TokenKind::GreaterEqual => { + self.emit_op(OpCode::OpLess); + self.emit_op(OpCode::OpNot) + } + + TokenKind::Less => self.emit_op(OpCode::OpLess), + TokenKind::LessEqual => { + self.emit_op(OpCode::OpGreater); + self.emit_op(OpCode::OpNot) + } + + _ => unreachable!("only called for binary operator tokens"), + }; + + Ok(()) + } + + fn literal(&mut self) -> LoxResult<()> { + match self.previous().kind { + TokenKind::Nil => self.emit_op(OpCode::OpNil), + TokenKind::True => self.emit_op(OpCode::OpTrue), + TokenKind::False => self.emit_op(OpCode::OpFalse), + _ => unreachable!("only called for literal value tokens"), + }; + + Ok(()) + } + + fn string(&mut self) -> LoxResult<()> { + let val = match &self.previous().kind { + TokenKind::String(s) => s.clone(), + _ => unreachable!("only called for strings"), + }; + + let id = self.strings.intern(val); + self.emit_constant(Value::String(id.into()), true); + + Ok(()) + } + + fn named_variable(&mut self, name: Token) -> LoxResult<()> { + let local_idx = self.resolve_local(&name); + + let ident = if local_idx.is_some() { + None + } else { + Some(self.identifier_constant(&name)?) + }; + + if self.match_token(&TokenKind::Equal) { + self.expression()?; + match local_idx { + Some(idx) => self.emit_op(OpCode::OpSetLocal(idx)), + None => self.emit_op(OpCode::OpSetGlobal(ident.unwrap())), + }; + } else { + match local_idx { + Some(idx) => self.emit_op(OpCode::OpGetLocal(idx)), + None => self.emit_op(OpCode::OpGetGlobal(ident.unwrap())), + }; + } + + Ok(()) + } + + fn variable(&mut self) -> LoxResult<()> { + let name = self.previous().clone(); + self.named_variable(name) + } + + fn parse_precedence(&mut self, precedence: Precedence) -> LoxResult<()> { + self.advance(); + let rule: ParseRule<T> = rule_for(&self.previous().kind); + let prefix_fn = match rule.prefix { + None => unimplemented!("expected expression or something, unclear"), + Some(func) => func, + }; + + prefix_fn(self)?; + + while precedence <= rule_for::<T>(&self.current().kind).precedence { + self.advance(); + match rule_for::<T>(&self.previous().kind).infix { + Some(func) => { + func(self)?; + } + None => { + unreachable!("invalid compiler state: error in parse rules") + } + } + } + + Ok(()) + } + + fn identifier_str(&mut self, token: &Token) -> LoxResult<InternedStr> { + let ident = match &token.kind { + TokenKind::Identifier(ident) => ident.to_string(), + _ => { + return Err(Error { + line: self.current().line, + kind: ErrorKind::ExpectedToken("Expected identifier"), + }) + } + }; + + Ok(self.strings.intern(ident)) + } + + fn identifier_constant(&mut self, name: &Token) -> LoxResult<ConstantIdx> { + let ident = self.identifier_str(name)?; + Ok(self.emit_constant(Value::String(ident.into()), false)) + } + + fn resolve_local(&self, name: &Token) -> Option<StackIdx> { + for (idx, local) in self.locals.locals.iter().enumerate().rev() { + if name.lexeme == local.name.lexeme { + if let Depth::Unitialised = local.depth { + // TODO(tazjin): *return* err + panic!("can't read variable in its own initialiser"); + } + return Some(StackIdx(idx)); + } + } + + None + } + + fn add_local(&mut self, name: Token) { + let local = Local { + name, + depth: Depth::Unitialised, + }; + + self.locals.locals.push(local); + } + + fn declare_variable(&mut self) -> LoxResult<()> { + if self.locals.scope_depth == 0 { + return Ok(()); + } + + let name = self.previous().clone(); + + for local in self.locals.locals.iter().rev() { + if local.depth.below(self.locals.scope_depth) { + break; + } + + if name.lexeme == local.name.lexeme { + return Err(Error { + kind: ErrorKind::VariableShadowed(name.lexeme.into()), + line: name.line, + }); + } + } + + self.add_local(name); + Ok(()) + } + + fn parse_variable(&mut self) -> LoxResult<Option<ConstantIdx>> { + consume!( + self, + TokenKind::Identifier(_), + ErrorKind::ExpectedToken("expected identifier") + ); + + self.declare_variable()?; + if self.locals.scope_depth > 0 { + return Ok(None); + } + + let name = self.previous().clone(); + let id = self.identifier_str(&name)?; + Ok(Some(self.emit_constant(Value::String(id.into()), false))) + } + + fn current_chunk(&mut self) -> &mut Chunk { + &mut self.chunk + } + + fn end_compiler(&mut self) -> LoxResult<()> { + self.emit_op(OpCode::OpReturn); + + #[cfg(feature = "disassemble")] + { + chunk::disassemble_chunk(&self.chunk); + println!("== compilation finished =="); + } + + Ok(()) + } + + fn emit_op(&mut self, op: OpCode) -> CodeIdx { + let line = self.previous().line; + self.current_chunk().add_op(op, line) + } + + fn emit_constant(&mut self, val: Value, with_op: bool) -> ConstantIdx { + let idx = ConstantIdx(self.chunk.add_constant(val)); + + if with_op { + self.emit_op(OpCode::OpConstant(idx)); + } + + idx + } + + fn patch_jump(&mut self, idx: CodeIdx) { + let offset = CodeOffset(self.chunk.code.len() - idx.0 - 1); + + if let OpCode::OpJumpPlaceholder(true) = self.chunk.code[idx.0] { + self.chunk.code[idx.0] = OpCode::OpJump(offset); + return; + } + + if let OpCode::OpJumpPlaceholder(false) = self.chunk.code[idx.0] { + self.chunk.code[idx.0] = OpCode::OpJumpIfFalse(offset); + return; + } + + panic!( + "attempted to patch unsupported op: {:?}", + self.chunk.code[idx.0] + ); + } + + fn previous(&self) -> &Token { + self.previous + .as_ref() + .expect("invalid internal compiler state: missing previous token") + } + + fn current(&self) -> &Token { + self.current + .as_ref() + .expect("invalid internal compiler state: missing current token") + } + + fn error_at(&mut self, line: usize, kind: ErrorKind) { + if self.panic { + return; + } + + self.panic = true; + self.errors.push(Error { kind, line }) + } + + fn match_token(&mut self, token: &TokenKind) -> bool { + if !self.check(token) { + return false; + } + + self.advance(); + true + } + + fn check(&self, token: &TokenKind) -> bool { + return self.current().kind == *token; + } + + fn synchronise(&mut self) { + self.panic = false; + + while self.current().kind != TokenKind::Eof { + if self.previous().kind == TokenKind::Semicolon { + return; + } + + match self.current().kind { + TokenKind::Class + | TokenKind::Fun + | TokenKind::Var + | TokenKind::For + | TokenKind::If + | TokenKind::While + | TokenKind::Print + | TokenKind::Return => return, + + _ => { + self.advance(); + } + } + } + } + + fn expect_semicolon(&mut self, msg: &'static str) -> LoxResult<()> { + consume!(self, TokenKind::Semicolon, ErrorKind::ExpectedToken(msg)); + Ok(()) + } +} + +pub fn compile(code: &str) -> Result<(Interner, Chunk), Vec<Error>> { + let chars = code.chars().collect::<Vec<char>>(); + let tokens = scanner::scan(&chars).map_err(|errors| { + errors.into_iter().map(Into::into).collect::<Vec<Error>>() + })?; + + let mut compiler = Compiler { + tokens: tokens.into_iter().peekable(), + chunk: Default::default(), + panic: false, + errors: vec![], + strings: Interner::with_capacity(1024), + locals: Default::default(), + current: None, + previous: None, + }; + + compiler.compile()?; + + if compiler.errors.is_empty() { + Ok((compiler.strings, compiler.chunk)) + } else { + Err(compiler.errors) + } +} diff --git a/users/tazjin/rlox/src/bytecode/errors.rs b/users/tazjin/rlox/src/bytecode/errors.rs new file mode 100644 index 000000000000..988031f763cf --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/errors.rs @@ -0,0 +1,51 @@ +use crate::scanner::ScannerError; + +use std::fmt; + +#[derive(Debug)] +pub enum ErrorKind { + UnexpectedChar(char), + UnterminatedString, + ExpectedToken(&'static str), + InternalError(&'static str), + TypeError(String), + VariableShadowed(String), +} + +#[derive(Debug)] +pub struct Error { + pub kind: ErrorKind, + pub line: usize, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[line NYI] Error: {:?}", self.kind) + } +} + +impl From<ScannerError> for Error { + fn from(err: ScannerError) -> Self { + match err { + ScannerError::UnexpectedChar { line, unexpected } => Error { + line, + kind: ErrorKind::UnexpectedChar(unexpected), + }, + + ScannerError::UnterminatedString { line } => Error { + line, + kind: ErrorKind::UnterminatedString, + }, + } + } +} + +// Convenience implementation as we're often dealing with vectors of +// errors (to report as many issues as possible before terminating) +impl From<Error> for Vec<Error> { + fn from(err: Error) -> Self { + vec![err] + } +} + +pub type LoxResult<T> = Result<T, Error>; diff --git a/users/tazjin/rlox/src/bytecode/interner/mod.rs b/users/tazjin/rlox/src/bytecode/interner/mod.rs new file mode 100644 index 000000000000..1da1a24b2c5f --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/interner/mod.rs @@ -0,0 +1,87 @@ +//! String-interning implementation for values that are likely to +//! benefit from fast comparisons and deduplication (e.g. instances of +//! variable names). +//! +//! This uses a trick from the typed-arena crate for guaranteeing +//! stable addresses by never resizing the existing String buffer, and +//! collecting full buffers in a vector. + +use std::collections::HashMap; + +#[cfg(test)] +mod tests; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct InternedStr { + id: usize, +} + +#[derive(Default)] +pub struct Interner { + map: HashMap<&'static str, InternedStr>, + vec: Vec<&'static str>, + buf: String, + full: Vec<String>, +} + +impl Interner { + pub fn with_capacity(cap: usize) -> Self { + Interner { + buf: String::with_capacity(cap), + ..Default::default() + } + } + + pub fn intern<S: AsRef<str>>(&mut self, name: S) -> InternedStr { + let name = name.as_ref(); + if let Some(&id) = self.map.get(name) { + return id; + } + + let name = self.alloc(name); + let id = InternedStr { + id: self.vec.len() as usize, + }; + + self.map.insert(name, id); + self.vec.push(name); + + debug_assert!(self.lookup(id) == name); + debug_assert!(self.intern(name) == id); + + id + } + + pub fn lookup<'a>(&'a self, id: InternedStr) -> &'a str { + self.vec[id.id] + } + + fn alloc<'a>(&'a mut self, name: &str) -> &'static str { + let cap = self.buf.capacity(); + if cap < self.buf.len() + name.len() { + let new_cap = (cap.max(name.len()) + 1).next_power_of_two(); + let new_buf = String::with_capacity(new_cap); + let old_buf = std::mem::replace(&mut self.buf, new_buf); + self.full.push(old_buf); + } + + let interned: &'a str = { + let start = self.buf.len(); + self.buf.push_str(name); + &self.buf[start..] + }; + + unsafe { + // This is sound for two reasons: + // + // 1. This function (Interner::alloc) is private, which + // prevents users from allocating a supposedly static + // reference. + // + // 2. Interner::lookup explicitly shortens the lifetime of + // references that are handed out to that of the + // reference to self. + return &*(interned as *const str); + } + } +} diff --git a/users/tazjin/rlox/src/bytecode/interner/tests.rs b/users/tazjin/rlox/src/bytecode/interner/tests.rs new file mode 100644 index 000000000000..b34bf6835389 --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/interner/tests.rs @@ -0,0 +1,24 @@ +use super::*; + +#[test] +fn interns_strings() { + let mut interner = Interner::with_capacity(128); + let id = interner.intern("hello world"); + assert_eq!("hello world", interner.lookup(id)); +} + +#[test] +fn deduplicates_strings() { + let mut interner = Interner::with_capacity(128); + let id_1 = interner.intern("hello world"); + let id_2 = interner.intern("hello world"); + assert_eq!(id_1, id_2); +} + +#[test] +fn ids_survive_growing() { + let mut interner = Interner::with_capacity(16); + let id = interner.intern("hello"); + interner.intern("excessively large string that will cause eallocation"); + assert_eq!("hello", interner.lookup(id)); +} diff --git a/users/tazjin/rlox/src/bytecode/mod.rs b/users/tazjin/rlox/src/bytecode/mod.rs new file mode 100644 index 000000000000..c6f3a737aef8 --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/mod.rs @@ -0,0 +1,33 @@ +//! Bytecode interpreter for Lox. +//! +//! https://craftinginterpreters.com/chunks-of-bytecode.html + +mod chunk; +mod compiler; +mod errors; +mod interner; +mod opcode; +mod value; +mod vm; + +#[cfg(test)] +mod tests; + +pub struct Interpreter {} + +impl crate::Lox for Interpreter { + type Error = errors::Error; + type Value = value::Value; + + fn create() -> Self { + Interpreter {} + } + + fn interpret( + &mut self, + code: String, + ) -> Result<Self::Value, Vec<Self::Error>> { + let (strings, chunk) = compiler::compile(&code)?; + vm::interpret(strings, chunk).map_err(|e| vec![e]) + } +} diff --git a/users/tazjin/rlox/src/bytecode/opcode.rs b/users/tazjin/rlox/src/bytecode/opcode.rs new file mode 100644 index 000000000000..8a106f96917d --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/opcode.rs @@ -0,0 +1,56 @@ +#[derive(Clone, Copy, Debug)] +pub struct ConstantIdx(pub usize); + +#[derive(Clone, Copy, Debug)] +pub struct StackIdx(pub usize); + +#[derive(Clone, Copy, Debug)] +pub struct CodeIdx(pub usize); + +#[derive(Clone, Copy, Debug)] +pub struct CodeOffset(pub usize); + +#[derive(Debug)] +pub enum OpCode { + /// Push a constant onto the stack. + OpConstant(ConstantIdx), + + // Literal pushes + OpNil, + OpTrue, + OpFalse, + + /// Return from the current function. + OpReturn, + + // Boolean & comparison operators + OpNot, + OpEqual, + OpGreater, + OpLess, + + /// Unary negation + OpNegate, + + // Arithmetic operators + OpAdd, + OpSubtract, + OpMultiply, + OpDivide, + + // Built in operations + OpPrint, + OpPop, + + // Variable management + OpDefineGlobal(ConstantIdx), + OpGetGlobal(ConstantIdx), + OpSetGlobal(ConstantIdx), + OpGetLocal(StackIdx), + OpSetLocal(StackIdx), + + // Control flow + OpJumpPlaceholder(bool), + OpJump(CodeOffset), + OpJumpIfFalse(CodeOffset), +} diff --git a/users/tazjin/rlox/src/bytecode/tests.rs b/users/tazjin/rlox/src/bytecode/tests.rs new file mode 100644 index 000000000000..bc7d6cb878f8 --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/tests.rs @@ -0,0 +1,152 @@ +use super::value::Value; +use super::*; + +use crate::Lox; + +fn expect(code: &str, value: Value) { + let result = Interpreter::create() + .interpret(code.into()) + .expect("evaluation failed"); + assert_eq!(result, value); +} + +fn expect_num(code: &str, value: f64) { + expect(code, Value::Number(value)) +} + +fn expect_bool(code: &str, value: bool) { + expect(code, Value::Bool(value)) +} + +fn expect_str(code: &str, value: &str) { + expect(code, Value::String(value.to_string().into())) +} + +#[test] +fn numbers() { + expect_num("1;", 1.0); + expect_num("13.37;", 13.37); +} + +#[test] +fn negative_numbers() { + // Note: This technically tests unary operators. + expect_num("-1;", -1.0); + expect_num("-13.37;", -13.37); +} + +#[test] +fn terms() { + expect_num("1 + 2;", 3.0); + expect_num("3 - 1;", 2.0); + expect_num("0.7 + 0.3;", 1.0); + expect_num("1 + -3;", -2.0); + expect_num("-1 - -1;", 0.0); + expect_num("10 - -10 + 10;", 30.0); +} + +#[test] +fn factors() { + expect_num("1 * 2;", 2.0); + expect_num("10 / 5;", 2.0); + expect_num("0.7 * 4 / 1.4;", 2.0); + expect_num("10 * -10 / 10;", -10.0); +} + +#[test] +fn arithmetic() { + expect_num("10 - 3 * 2;", 4.0); + expect_num("-4 * -4 + (14 - 5);", 25.0); + expect_num("(702 + 408) - ((239 - 734) / -5) + -4;", 1007.0); +} + +#[test] +fn trivial_literals() { + expect("true;", Value::Bool(true)); + expect("false;", Value::Bool(false)); + expect("nil;", Value::Nil); +} + +#[test] +fn negation() { + expect_bool("!true;", false); + expect_bool("!false;", true); + expect_bool("!nil;", true); + expect_bool("!13.5;", false); + expect_bool("!-42;", false); +} + +#[test] +fn equality() { + expect_bool("42 == 42;", true); + expect_bool("42 != 42;", false); + expect_bool("42 == 42.0;", true); + + expect_bool("true == true;", true); + expect_bool("true == false;", false); + expect_bool("true == !false;", true); + expect_bool("true != true;", false); + expect_bool("true != false;", true); + + expect_bool("42 == false;", false); + expect_bool("42 == true;", false); + expect_bool("!42 == !true;", true); +} + +#[test] +fn comparisons() { + expect_bool("42 > 23;", true); + expect_bool("42 < 23;", false); + expect_bool("42 <= 42;", true); + expect_bool("42 <= 23;", false); + expect_bool("42 >= 42;", true); + expect_bool("42 >= 23;", true); +} + +#[test] +fn strings() { + expect_str("\"hello\";", "hello"); + expect_str("\"hello\" + \" world\";", "hello world"); +} + +#[test] +fn global_variables() { + expect_num("var a = 5; a;", 5.0); + expect_num("var a = 5; var b = 2; a * b;", 10.0); + expect_str( + "var greeting = \"hello\"; var name = \"Zubnog\"; greeting + \" \" + name;", + "hello Zubnog", + ); +} + +#[test] +fn global_assignment() { + expect_str( + r#" + var breakfast = "beignets"; + var beverage = "cafe au lait"; + breakfast = "beignets with " + beverage; + breakfast; + "#, + "beignets with cafe au lait", + ); +} + +#[test] +fn local_variables() { + expect_num( + r#" + var a = 10; + var b = 5; + var result = 0; + { + var b = 10; + var c = 2; + result = a * b * c; + } + + result; + "#, + 200.0, + ); +} diff --git a/users/tazjin/rlox/src/bytecode/value.rs b/users/tazjin/rlox/src/bytecode/value.rs new file mode 100644 index 000000000000..4170efadf8fe --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/value.rs @@ -0,0 +1,37 @@ +use super::interner::InternedStr; + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Nil, + Bool(bool), + Number(f64), + String(LoxString), +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum LoxString { + Heap(String), + Interned(InternedStr), +} + +impl From<String> for LoxString { + fn from(s: String) -> Self { + LoxString::Heap(s) + } +} + +impl From<InternedStr> for LoxString { + fn from(s: InternedStr) -> Self { + LoxString::Interned(s) + } +} + +impl Value { + pub fn is_falsey(&self) -> bool { + match self { + Value::Nil => true, + Value::Bool(false) => true, + _ => false, + } + } +} diff --git a/users/tazjin/rlox/src/bytecode/vm.rs b/users/tazjin/rlox/src/bytecode/vm.rs new file mode 100644 index 000000000000..d287ec7cb8c5 --- /dev/null +++ b/users/tazjin/rlox/src/bytecode/vm.rs @@ -0,0 +1,277 @@ +use std::collections::HashMap; + +use super::chunk; +use super::errors::*; +use super::interner::Interner; +use super::opcode::OpCode; +use super::value::{LoxString, Value}; + +pub struct VM { + chunk: chunk::Chunk, + + // TODO(tazjin): Accessing array elements constantly is not ideal, + // lets see if something clever can be done with iterators. + ip: usize, + + stack: Vec<Value>, + strings: Interner, + + globals: HashMap<LoxString, Value>, + + // Operations that consume values from the stack without pushing + // anything leave their last value in this slot, which makes it + // possible to return values from interpreters that ran code which + // ended with a statement. + last_drop: Option<Value>, +} + +impl VM { + fn push(&mut self, value: Value) { + self.stack.push(value) + } + + fn pop(&mut self) -> Value { + self.stack.pop().expect("fatal error: stack empty!") + } +} + +macro_rules! with_type { + ( $self:ident, $val:ident, $type:pat, $body:expr ) => { + match $val { + $type => $body, + _ => { + return Err(Error { + line: $self.chunk.get_line($self.ip - 1), + kind: ErrorKind::TypeError(format!( + "Expected type {}, but found value: {:?}", + stringify!($type), + $val, + )), + }) + } + } + }; +} + +macro_rules! binary_op { + ( $vm:ident, $type:tt, $op:tt ) => { + binary_op!($vm, $type, $type, $op) + }; + + ( $vm:ident, $in_type:tt, $out_type:tt, $op:tt ) => {{ + let b = $vm.pop(); + let a = $vm.pop(); + + with_type!($vm, b, Value::$in_type(val_b), { + with_type!($vm, a, Value::$in_type(val_a), { + $vm.push(Value::$out_type(val_a $op val_b)) + }) + }) + }}; +} + +impl VM { + fn run(&mut self) -> LoxResult<Value> { + loop { + let op = &self.chunk.code[self.ip]; + + #[cfg(feature = "disassemble")] + chunk::disassemble_instruction(&self.chunk, self.ip); + + self.ip += 1; + + match op { + OpCode::OpReturn => { + if !self.stack.is_empty() { + let val = self.pop(); + return Ok(self.return_value(val)); + } else if self.last_drop.is_some() { + let val = self.last_drop.take().unwrap(); + return Ok(self.return_value(val)); + } else { + return Ok(Value::Nil); + } + } + + OpCode::OpConstant(idx) => { + let c = self.chunk.constant(*idx).clone(); + self.push(c); + } + + OpCode::OpNil => self.push(Value::Nil), + OpCode::OpTrue => self.push(Value::Bool(true)), + OpCode::OpFalse => self.push(Value::Bool(false)), + + OpCode::OpNot => { + let v = self.pop(); + self.push(Value::Bool(v.is_falsey())); + } + + OpCode::OpEqual => { + let b = self.pop(); + let a = self.pop(); + self.push(Value::Bool(a == b)); + } + + OpCode::OpLess => binary_op!(self, Number, Bool, <), + OpCode::OpGreater => binary_op!(self, Number, Bool, >), + + OpCode::OpNegate => { + let v = self.pop(); + with_type!( + self, + v, + Value::Number(num), + self.push(Value::Number(-num)) + ); + } + + OpCode::OpSubtract => binary_op!(self, Number, -), + OpCode::OpMultiply => binary_op!(self, Number, *), + OpCode::OpDivide => binary_op!(self, Number, /), + + OpCode::OpAdd => { + let b = self.pop(); + let a = self.pop(); + + match (a, b) { + (Value::String(s_a), Value::String(s_b)) => { + let mut new_s = self.resolve_str(&s_a).to_string(); + new_s.push_str(self.resolve_str(&s_b)); + self.push(Value::String(new_s.into())); + } + + (Value::Number(n_a), Value::Number(n_b)) => + self.push(Value::Number(n_a + n_b)), + + _ => return Err(Error { + line: self.chunk.get_line(self.ip - 1), + kind: ErrorKind::TypeError( + "'+' operator only works on strings and numbers".into() + ), + }) + } + } + + OpCode::OpPrint => { + let val = self.pop(); + println!("{}", self.print_value(val)); + } + + OpCode::OpPop => { + self.last_drop = Some(self.pop()); + } + + OpCode::OpDefineGlobal(name_idx) => { + let name = self.chunk.constant(*name_idx); + with_type!(self, name, Value::String(name), { + let name = name.clone(); + let val = self.pop(); + self.globals.insert(name, val); + }); + } + + OpCode::OpGetGlobal(name_idx) => { + let name = self.chunk.constant(*name_idx); + with_type!(self, name, Value::String(name), { + let val = match self.globals.get(name) { + None => unimplemented!("variable not found error"), + Some(val) => val.clone(), + }; + self.push(val) + }); + } + + OpCode::OpSetGlobal(name_idx) => { + let name = self.chunk.constant(*name_idx).clone(); + let new_val = self.pop(); + with_type!(self, name, Value::String(name), { + match self.globals.get_mut(&name) { + None => unimplemented!("variable not found error"), + Some(val) => { + *val = new_val; + } + } + }); + } + + OpCode::OpGetLocal(local_idx) => { + let value = self.stack[local_idx.0].clone(); + self.push(value); + } + + OpCode::OpSetLocal(local_idx) => { + debug_assert!( + self.stack.len() > local_idx.0, + "stack is not currently large enough for local" + ); + self.stack[local_idx.0] = + self.stack.last().unwrap().clone(); + } + + OpCode::OpJumpPlaceholder(_) => { + panic!("unpatched jump detected - this is a fatal compiler error!"); + } + + OpCode::OpJump(offset) => { + self.ip += offset.0; + } + + OpCode::OpJumpIfFalse(offset) => { + if self + .stack + .last() + .expect("condition should leave a value on the stack") + .is_falsey() + { + self.ip += offset.0; + } + } + } + + #[cfg(feature = "disassemble")] + println!("=> {:?}", self.stack); + } + } + + // For some types of values (e.g. interned strings), returns + // should no longer include any references into the interpreter. + fn return_value(&self, val: Value) -> Value { + match val { + Value::String(string @ LoxString::Interned(_)) => { + Value::String(self.resolve_str(&string).to_string().into()) + } + _ => val, + } + } + + fn resolve_str<'a>(&'a self, string: &'a LoxString) -> &'a str { + match string { + LoxString::Heap(s) => s.as_str(), + LoxString::Interned(id) => self.strings.lookup(*id), + } + } + + fn print_value(&self, val: Value) -> String { + match val { + Value::String(LoxString::Heap(s)) => s, + Value::String(LoxString::Interned(id)) => { + self.strings.lookup(id).into() + } + _ => format!("{:?}", val), + } + } +} + +pub fn interpret(strings: Interner, chunk: chunk::Chunk) -> LoxResult<Value> { + let mut vm = VM { + chunk, + strings, + globals: HashMap::new(), + ip: 0, + stack: vec![], + last_drop: None, + }; + + vm.run() +} diff --git a/users/tazjin/rlox/src/main.rs b/users/tazjin/rlox/src/main.rs new file mode 100644 index 000000000000..2d8cf4f354ea --- /dev/null +++ b/users/tazjin/rlox/src/main.rs @@ -0,0 +1,80 @@ +use std::env; +use std::fs; +use std::io; +use std::io::Write; +use std::process; + +mod bytecode; +mod scanner; +mod treewalk; + +/// Trait for making the different interpreters callable in the same +/// way. +pub trait Lox { + type Value: std::fmt::Debug; + type Error: std::fmt::Display; + + fn create() -> Self; + fn interpret( + &mut self, + source: String, + ) -> Result<Self::Value, Vec<Self::Error>>; +} + +fn main() { + let mut args = env::args(); + if args.len() > 2 { + println!("Usage: rlox [script]"); + process::exit(1); + } + + match env::var("LOX_INTERPRETER").as_ref().map(String::as_str) { + Ok("treewalk") => { + pick::<treewalk::interpreter::Interpreter>(args.nth(1)) + } + _ => pick::<bytecode::Interpreter>(args.nth(1)), + } +} + +fn pick<I: Lox>(file_arg: Option<String>) { + if let Some(file) = file_arg { + run_file::<I>(&file); + } else { + run_prompt::<I>(); + } +} + +// Run Lox code from a file and print results to stdout +fn run_file<I: Lox>(file: &str) { + let contents = + fs::read_to_string(file).expect("failed to read the input file"); + let mut lox = I::create(); + run(&mut lox, contents); +} + +// Evaluate Lox code interactively in a shitty REPL. +fn run_prompt<I: Lox>() { + let mut line = String::new(); + let mut lox = I::create(); + + loop { + print!("> "); + io::stdout().flush().unwrap(); + io::stdin() + .read_line(&mut line) + .expect("failed to read user input"); + run(&mut lox, std::mem::take(&mut line)); + line.clear(); + } +} + +fn run<I: Lox>(lox: &mut I, code: String) { + match lox.interpret(code) { + Ok(result) => println!("=> {:?}", result), + Err(errors) => { + for error in errors { + eprintln!("{}", error); + } + } + } +} diff --git a/users/tazjin/rlox/src/scanner.rs b/users/tazjin/rlox/src/scanner.rs new file mode 100644 index 000000000000..4e8f07b61f5e --- /dev/null +++ b/users/tazjin/rlox/src/scanner.rs @@ -0,0 +1,291 @@ +#[derive(Clone, Debug, PartialEq)] +pub enum TokenKind { + // Single-character tokens. + LeftParen, + RightParen, + LeftBrace, + RightBrace, + Comma, + Dot, + Minus, + Plus, + Semicolon, + Slash, + Star, + + // One or two character tokens. + Bang, + BangEqual, + Equal, + EqualEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + + // Literals. + Identifier(String), + String(String), + Number(f64), + True, + False, + Nil, + + // Keywords. + And, + Class, + Else, + Fun, + For, + If, + Or, + Print, + Return, + Super, + This, + Var, + While, + + // Special things + Eof, +} + +#[derive(Clone, Debug)] +pub struct Token { + pub kind: TokenKind, + pub lexeme: String, + pub line: usize, +} + +pub enum ScannerError { + UnexpectedChar { line: usize, unexpected: char }, + UnterminatedString { line: usize }, +} + +struct Scanner<'a> { + source: &'a [char], + tokens: Vec<Token>, + errors: Vec<ScannerError>, + start: usize, // offset of first character in current lexeme + current: usize, // current offset into source + line: usize, // current line in source +} + +impl<'a> Scanner<'a> { + fn is_at_end(&self) -> bool { + return self.current >= self.source.len(); + } + + fn advance(&mut self) -> char { + self.current += 1; + self.source[self.current - 1] + } + + fn add_token(&mut self, kind: TokenKind) { + let lexeme = &self.source[self.start..self.current]; + self.tokens.push(Token { + kind, + lexeme: lexeme.into_iter().collect(), + line: self.line, + }) + } + + fn scan_token(&mut self) { + match self.advance() { + // simple single-character tokens + '(' => self.add_token(TokenKind::LeftParen), + ')' => self.add_token(TokenKind::RightParen), + '{' => self.add_token(TokenKind::LeftBrace), + '}' => self.add_token(TokenKind::RightBrace), + ',' => self.add_token(TokenKind::Comma), + '.' => self.add_token(TokenKind::Dot), + '-' => self.add_token(TokenKind::Minus), + '+' => self.add_token(TokenKind::Plus), + ';' => self.add_token(TokenKind::Semicolon), + '*' => self.add_token(TokenKind::Star), + + // possible multi-character tokens + '!' => self.add_if_next('=', TokenKind::BangEqual, TokenKind::Bang), + '=' => { + self.add_if_next('=', TokenKind::EqualEqual, TokenKind::Equal) + } + '<' => self.add_if_next('=', TokenKind::LessEqual, TokenKind::Less), + '>' => self.add_if_next( + '=', + TokenKind::GreaterEqual, + TokenKind::Greater, + ), + + '/' => { + // support comments until EOL by discarding characters + if self.match_next('/') { + while self.peek() != '\n' && !self.is_at_end() { + self.advance(); + } + } else { + self.add_token(TokenKind::Slash); + } + } + + // ignore whitespace + ws if ws.is_whitespace() => { + if ws == '\n' { + self.line += 1 + } + } + + '"' => self.scan_string(), + + digit if digit.is_digit(10) => self.scan_number(), + + chr if chr.is_alphabetic() || chr == '_' => self.scan_identifier(), + + unexpected => self.errors.push(ScannerError::UnexpectedChar { + line: self.line, + unexpected, + }), + }; + } + + fn match_next(&mut self, expected: char) -> bool { + if self.is_at_end() || self.source[self.current] != expected { + false + } else { + self.current += 1; + true + } + } + + fn add_if_next(&mut self, expected: char, then: TokenKind, or: TokenKind) { + if self.match_next(expected) { + self.add_token(then); + } else { + self.add_token(or); + } + } + + fn peek(&self) -> char { + if self.is_at_end() { + return '\0'; + } else { + return self.source[self.current]; + } + } + + fn peek_next(&self) -> char { + if self.current + 1 >= self.source.len() { + return '\0'; + } else { + return self.source[self.current + 1]; + } + } + + fn scan_string(&mut self) { + while self.peek() != '"' && !self.is_at_end() { + if self.peek() == '\n' { + self.line += 1; + } + + self.advance(); + } + + if self.is_at_end() { + self.errors + .push(ScannerError::UnterminatedString { line: self.line }); + return; + } + + // closing '"' + self.advance(); + + // add token without surrounding quotes + let string: String = self.source[(self.start + 1)..(self.current - 1)] + .iter() + .collect(); + self.add_token(TokenKind::String(string)); + } + + fn scan_number(&mut self) { + while self.peek().is_digit(10) { + self.advance(); + } + + // Look for a fractional part + if self.peek() == '.' && self.peek_next().is_digit(10) { + // consume '.' + self.advance(); + + while self.peek().is_digit(10) { + self.advance(); + } + } + + let num: f64 = self.source[self.start..self.current] + .iter() + .collect::<String>() + .parse() + .expect("float parsing should always work"); + + self.add_token(TokenKind::Number(num)); + } + + fn scan_identifier(&mut self) { + while self.peek().is_alphanumeric() || self.peek() == '_' { + self.advance(); + } + + let ident: String = + self.source[self.start..self.current].iter().collect(); + + // Determine whether this is an identifier, or a keyword: + let token_kind = match ident.as_str() { + "and" => TokenKind::And, + "class" => TokenKind::Class, + "else" => TokenKind::Else, + "false" => TokenKind::False, + "for" => TokenKind::For, + "fun" => TokenKind::Fun, + "if" => TokenKind::If, + "nil" => TokenKind::Nil, + "or" => TokenKind::Or, + "print" => TokenKind::Print, + "return" => TokenKind::Return, + "super" => TokenKind::Super, + "this" => TokenKind::This, + "true" => TokenKind::True, + "var" => TokenKind::Var, + "while" => TokenKind::While, + _ => TokenKind::Identifier(ident), + }; + + self.add_token(token_kind); + } + + fn scan_tokens(&mut self) { + while !self.is_at_end() { + self.start = self.current; + self.scan_token(); + } + + self.add_token(TokenKind::Eof); + } +} + +pub fn scan<'a>(input: &'a [char]) -> Result<Vec<Token>, Vec<ScannerError>> { + let mut scanner = Scanner { + source: &input, + tokens: vec![], + errors: vec![], + start: 0, + current: 0, + line: 0, + }; + + scanner.scan_tokens(); + + if !scanner.errors.is_empty() { + return Err(scanner.errors); + } + + return Ok(scanner.tokens); +} diff --git a/users/tazjin/rlox/src/treewalk/errors.rs b/users/tazjin/rlox/src/treewalk/errors.rs new file mode 100644 index 000000000000..391663d51b18 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/errors.rs @@ -0,0 +1,59 @@ +use crate::scanner::ScannerError; +use crate::treewalk::interpreter::Value; + +use std::fmt; + +#[derive(Debug)] +pub enum ErrorKind { + UnexpectedChar(char), + UnterminatedString, + UnmatchedParens, + ExpectedExpression(String), + ExpectedSemicolon, + ExpectedClosingBrace, + ExpectedToken(&'static str), + TypeError(String), + UndefinedVariable(String), + InternalError(String), + InvalidAssignmentTarget(String), + RuntimeError(String), + StaticError(String), + + // This variant is not an error, rather it is used for + // short-circuiting out of a function body that hits a `return` + // statement. + // + // It's implemented this way because in the original book the + // author uses exceptions for control flow, and this is the + // closest equivalent that I had available without diverging too + // much. + FunctionReturn(Value), +} + +#[derive(Debug)] +pub struct Error { + pub line: usize, + pub kind: ErrorKind, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[line {}] Error: {:?}", self.line, self.kind) + } +} + +impl From<ScannerError> for Error { + fn from(err: ScannerError) -> Self { + match err { + ScannerError::UnexpectedChar { line, unexpected } => Error { + line, + kind: ErrorKind::UnexpectedChar(unexpected), + }, + + ScannerError::UnterminatedString { line } => Error { + line, + kind: ErrorKind::UnterminatedString, + }, + } + } +} diff --git a/users/tazjin/rlox/src/treewalk/interpreter.rs b/users/tazjin/rlox/src/treewalk/interpreter.rs new file mode 100644 index 000000000000..d9fe33661684 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/interpreter.rs @@ -0,0 +1,556 @@ +use crate::treewalk::errors::{Error, ErrorKind}; +use crate::treewalk::parser::{self, Block, Expr, Literal, Statement}; +use crate::treewalk::resolver; +use crate::treewalk::scanner::{self, TokenKind}; +use crate::Lox; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::RwLock; + +// Implementation of built-in functions. +mod builtins; + +#[cfg(test)] +mod tests; + +// Tree-walk interpreter + +// Representation of all callables, including builtins & user-defined +// functions. +#[derive(Clone, Debug)] +pub enum Callable { + Builtin(&'static dyn builtins::Builtin), + Function { + func: Rc<parser::Function>, + closure: Rc<RwLock<Environment>>, + }, +} + +impl Callable { + fn arity(&self) -> usize { + match self { + Callable::Builtin(builtin) => builtin.arity(), + Callable::Function { func, .. } => func.params.len(), + } + } + + fn call( + &self, + lox: &mut Interpreter, + args: Vec<Value>, + ) -> Result<Value, Error> { + match self { + Callable::Builtin(builtin) => builtin.call(args), + + Callable::Function { func, closure } => { + let mut fn_env: Environment = Default::default(); + fn_env.enclosing = Some(closure.clone()); + + for (param, value) in func.params.iter().zip(args.into_iter()) { + fn_env.define(param, value)?; + } + + let result = lox.interpret_block_with_env( + Some(Rc::new(RwLock::new(fn_env))), + &func.body, + ); + + match result { + // extract returned values if applicable + Err(Error { + kind: ErrorKind::FunctionReturn(value), + .. + }) => Ok(value), + + // otherwise just return the result itself + _ => result, + } + } + } + } +} + +// Representation of an in-language value. +#[derive(Clone, Debug)] +pub enum Value { + Literal(Literal), + Callable(Callable), +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Literal(lhs), Value::Literal(rhs)) => lhs == rhs, + // functions do not have equality + _ => false, + } + } +} + +impl From<Literal> for Value { + fn from(lit: Literal) -> Value { + Value::Literal(lit) + } +} + +impl Value { + fn expect_literal(self) -> Result<Literal, Error> { + match self { + Value::Literal(lit) => Ok(lit), + _ => unimplemented!(), // which error? which line? + } + } +} + +#[derive(Debug, Default)] +pub struct Environment { + enclosing: Option<Rc<RwLock<Environment>>>, + values: HashMap<String, Value>, +} + +impl Environment { + fn define( + &mut self, + name: &scanner::Token, + value: Value, + ) -> Result<(), Error> { + let ident = identifier_str(name)?; + self.values.insert(ident.into(), value); + Ok(()) + } + + fn get( + &self, + ident: &str, + line: usize, + depth: usize, + ) -> Result<Value, Error> { + if depth > 0 { + match &self.enclosing { + None => { + return Err(Error { + line, + kind: ErrorKind::InternalError(format!( + "invalid depth {} for {}", + depth, ident + )), + }) + } + Some(parent) => { + let env = parent + .read() + .expect("fatal: environment lock poisoned"); + return env.get(ident, line, depth - 1); + } + } + } + + self.values + .get(ident) + .map(Clone::clone) + .ok_or_else(|| Error { + line, + kind: ErrorKind::UndefinedVariable(ident.into()), + }) + } + + fn assign( + &mut self, + name: &scanner::Token, + value: Value, + ) -> Result<(), Error> { + let ident = identifier_str(name)?; + + match self.values.get_mut(ident) { + Some(target) => { + *target = value; + Ok(()) + } + None => { + if let Some(parent) = &self.enclosing { + return parent.write().unwrap().assign(name, value); + } + + Err(Error { + line: name.line, + kind: ErrorKind::UndefinedVariable(ident.into()), + }) + } + } + } +} + +fn identifier_str(name: &scanner::Token) -> Result<&str, Error> { + if let TokenKind::Identifier(ident) = &name.kind { + Ok(ident) + } else { + Err(Error { + line: name.line, + kind: ErrorKind::InternalError("unexpected identifier kind".into()), + }) + } +} + +#[derive(Debug)] +pub struct Interpreter { + env: Rc<RwLock<Environment>>, +} + +impl Lox for Interpreter { + type Value = Value; + type Error = Error; + + /// Create a new interpreter and configure the initial global + /// variable set. + fn create() -> Self { + let mut globals = HashMap::new(); + + globals.insert( + "clock".into(), + Value::Callable(Callable::Builtin(&builtins::Clock {})), + ); + + Interpreter { + env: Rc::new(RwLock::new(Environment { + enclosing: None, + values: globals, + })), + } + } + + fn interpret(&mut self, code: String) -> Result<Value, Vec<Error>> { + let chars: Vec<char> = code.chars().collect(); + + let mut program = scanner::scan(&chars) + .map_err(|errors| errors.into_iter().map(Into::into).collect()) + .and_then(|tokens| parser::parse(tokens))?; + + let globals = self + .env + .read() + .expect("static globals lock poisoned") + .values + .keys() + .map(Clone::clone) + .collect::<Vec<String>>(); + + resolver::resolve(&globals, &mut program).map_err(|e| vec![e])?; + self.interpret_block_with_env(None, &program) + .map_err(|e| vec![e]) + } +} + +impl Interpreter { + // Environment modification helpers + fn define_var( + &mut self, + name: &scanner::Token, + value: Value, + ) -> Result<(), Error> { + self.env + .write() + .expect("environment lock is poisoned") + .define(name, value) + } + + fn assign_var( + &mut self, + name: &scanner::Token, + value: Value, + ) -> Result<(), Error> { + self.env + .write() + .expect("environment lock is poisoned") + .assign(name, value) + } + + fn get_var(&mut self, var: &parser::Variable) -> Result<Value, Error> { + let ident = identifier_str(&var.name)?; + let depth = var.depth.ok_or_else(|| Error { + line: var.name.line, + kind: ErrorKind::UndefinedVariable(ident.into()), + })?; + + self.env.read().expect("environment lock is poisoned").get( + ident, + var.name.line, + depth, + ) + } + + /// Interpret the block in the supplied environment. If no + /// environment is supplied, a new one is created using the + /// current one as its parent. + fn interpret_block_with_env( + &mut self, + env: Option<Rc<RwLock<Environment>>>, + block: &parser::Block, + ) -> Result<Value, Error> { + let env = match env { + Some(env) => env, + None => { + let env: Rc<RwLock<Environment>> = Default::default(); + set_enclosing_env(&env, self.env.clone()); + env + } + }; + + let previous = std::mem::replace(&mut self.env, env); + let result = self.interpret_block(block); + + // Swap it back, discarding the child env. + self.env = previous; + + return result; + } + + fn interpret_block(&mut self, program: &Block) -> Result<Value, Error> { + let mut value = Value::Literal(Literal::Nil); + + for stmt in program { + value = self.interpret_stmt(stmt)?; + } + + Ok(value) + } + + fn interpret_stmt(&mut self, stmt: &Statement) -> Result<Value, Error> { + let value = match stmt { + Statement::Expr(expr) => self.eval(expr)?, + Statement::Print(expr) => { + let result = self.eval(expr)?; + let output = format!("{:?}", result); + println!("{}", output); + Value::Literal(Literal::String(output)) + } + Statement::Var(var) => return self.interpret_var(var), + Statement::Block(block) => { + return self.interpret_block_with_env(None, block) + } + Statement::If(if_stmt) => return self.interpret_if(if_stmt), + Statement::While(while_stmt) => { + return self.interpret_while(while_stmt) + } + Statement::Function(func) => { + return self.interpret_function(func.clone()) + } + Statement::Return(ret) => { + return Err(Error { + line: 0, + kind: ErrorKind::FunctionReturn(self.eval(&ret.value)?), + }) + } + }; + + Ok(value) + } + + fn interpret_var(&mut self, var: &parser::Var) -> Result<Value, Error> { + let init = var.initialiser.as_ref().ok_or_else(|| Error { + line: var.name.line, + kind: ErrorKind::InternalError( + "missing variable initialiser".into(), + ), + })?; + let value = self.eval(init)?; + self.define_var(&var.name, value.clone())?; + Ok(value) + } + + fn interpret_if(&mut self, if_stmt: &parser::If) -> Result<Value, Error> { + let condition = self.eval(&if_stmt.condition)?; + + if eval_truthy(&condition) { + self.interpret_stmt(&if_stmt.then_branch) + } else if let Some(else_branch) = &if_stmt.else_branch { + self.interpret_stmt(else_branch) + } else { + Ok(Value::Literal(Literal::Nil)) + } + } + + fn interpret_while( + &mut self, + stmt: &parser::While, + ) -> Result<Value, Error> { + let mut value = Value::Literal(Literal::Nil); + while eval_truthy(&self.eval(&stmt.condition)?) { + value = self.interpret_stmt(&stmt.body)?; + } + + Ok(value) + } + + fn interpret_function( + &mut self, + func: Rc<parser::Function>, + ) -> Result<Value, Error> { + let name = func.name.clone(); + let value = Value::Callable(Callable::Function { + func, + closure: self.env.clone(), + }); + self.define_var(&name, value.clone())?; + Ok(value) + } + + fn eval(&mut self, expr: &Expr) -> Result<Value, Error> { + match expr { + Expr::Assign(assign) => self.eval_assign(assign), + Expr::Literal(lit) => Ok(lit.clone().into()), + Expr::Grouping(grouping) => self.eval(&*grouping.0), + Expr::Unary(unary) => self.eval_unary(unary), + Expr::Binary(binary) => self.eval_binary(binary), + Expr::Variable(var) => self.get_var(var), + Expr::Logical(log) => self.eval_logical(log), + Expr::Call(call) => self.eval_call(call), + } + } + + fn eval_unary(&mut self, expr: &parser::Unary) -> Result<Value, Error> { + let right = self.eval(&*expr.right)?; + + match (&expr.operator.kind, right) { + (TokenKind::Minus, Value::Literal(Literal::Number(num))) => { + Ok(Literal::Number(-num).into()) + } + (TokenKind::Bang, right) => { + Ok(Literal::Boolean(!eval_truthy(&right)).into()) + } + + (op, right) => Err(Error { + line: expr.operator.line, + kind: ErrorKind::TypeError(format!( + "Operator '{:?}' can not be called with argument '{:?}'", + op, right + )), + }), + } + } + + fn eval_binary(&mut self, expr: &parser::Binary) -> Result<Value, Error> { + let left = self.eval(&*expr.left)?.expect_literal()?; + let right = self.eval(&*expr.right)?.expect_literal()?; + + let result = match (&expr.operator.kind, left, right) { + // Numeric + (TokenKind::Minus, Literal::Number(l), Literal::Number(r)) => Literal::Number(l - r), + (TokenKind::Slash, Literal::Number(l), Literal::Number(r)) => Literal::Number(l / r), + (TokenKind::Star, Literal::Number(l), Literal::Number(r)) => Literal::Number(l * r), + (TokenKind::Plus, Literal::Number(l), Literal::Number(r)) => Literal::Number(l + r), + + // Strings + (TokenKind::Plus, Literal::String(l), Literal::String(r)) => { + Literal::String(format!("{}{}", l, r)) + } + + // Comparators (on numbers only?) + (TokenKind::Greater, Literal::Number(l), Literal::Number(r)) => Literal::Boolean(l > r), + (TokenKind::GreaterEqual, Literal::Number(l), Literal::Number(r)) => { + Literal::Boolean(l >= r) + } + (TokenKind::Less, Literal::Number(l), Literal::Number(r)) => Literal::Boolean(l < r), + (TokenKind::LessEqual, Literal::Number(l), Literal::Number(r)) => { + Literal::Boolean(l <= r) + } + + // Equality + (TokenKind::Equal, l, r) => Literal::Boolean(l == r), + (TokenKind::BangEqual, l, r) => Literal::Boolean(l != r), + + (op, left, right) => { + return Err(Error { + line: expr.operator.line, + kind: ErrorKind::TypeError(format!( + "Operator '{:?}' can not be called with arguments '({:?}, {:?})'", + op, left, right + )), + }) + } + }; + + Ok(result.into()) + } + + fn eval_assign(&mut self, assign: &parser::Assign) -> Result<Value, Error> { + let value = self.eval(&assign.value)?; + self.assign_var(&assign.name, value.clone())?; + Ok(value) + } + + fn eval_logical( + &mut self, + logical: &parser::Logical, + ) -> Result<Value, Error> { + let left = eval_truthy(&self.eval(&logical.left)?); + let right = eval_truthy(&self.eval(&logical.right)?); + + match &logical.operator.kind { + TokenKind::And => Ok(Literal::Boolean(left && right).into()), + TokenKind::Or => Ok(Literal::Boolean(left || right).into()), + kind => Err(Error { + line: logical.operator.line, + kind: ErrorKind::InternalError(format!( + "Invalid logical operator: {:?}", + kind + )), + }), + } + } + + fn eval_call(&mut self, call: &parser::Call) -> Result<Value, Error> { + let callable = match self.eval(&call.callee)? { + Value::Callable(c) => c, + Value::Literal(v) => { + return Err(Error { + line: call.paren.line, + kind: ErrorKind::RuntimeError(format!( + "not callable: {:?}", + v + )), + }) + } + }; + + let mut args = vec![]; + for arg in &call.args { + args.push(self.eval(arg)?); + } + + if callable.arity() != args.len() { + return Err(Error { + line: call.paren.line, + kind: ErrorKind::RuntimeError(format!( + "Expected {} arguments, but got {}", + callable.arity(), + args.len(), + )), + }); + } + + callable.call(self, args) + } +} + +// Interpreter functions not dependent on interpreter-state. + +fn eval_truthy(lit: &Value) -> bool { + if let Value::Literal(lit) = lit { + match lit { + Literal::Nil => false, + Literal::Boolean(b) => *b, + _ => true, + } + } else { + false + } +} + +fn set_enclosing_env( + this: &RwLock<Environment>, + parent: Rc<RwLock<Environment>>, +) { + this.write() + .expect("environment lock is poisoned") + .enclosing = Some(parent); +} diff --git a/users/tazjin/rlox/src/treewalk/interpreter/builtins.rs b/users/tazjin/rlox/src/treewalk/interpreter/builtins.rs new file mode 100644 index 000000000000..c502d2a1718a --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/interpreter/builtins.rs @@ -0,0 +1,25 @@ +use std::fmt; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::treewalk::errors::Error; +use crate::treewalk::interpreter::Value; +use crate::treewalk::parser::Literal; + +pub trait Builtin: fmt::Debug { + fn arity(&self) -> usize; + fn call(&self, args: Vec<Value>) -> Result<Value, Error>; +} + +// Builtin to return the current timestamp. +#[derive(Debug)] +pub struct Clock {} +impl Builtin for Clock { + fn arity(&self) -> usize { + 0 + } + + fn call(&self, _args: Vec<Value>) -> Result<Value, Error> { + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + Ok(Value::Literal(Literal::Number(now.as_secs() as f64))) + } +} diff --git a/users/tazjin/rlox/src/treewalk/interpreter/tests.rs b/users/tazjin/rlox/src/treewalk/interpreter/tests.rs new file mode 100644 index 000000000000..2fc6f4fee978 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/interpreter/tests.rs @@ -0,0 +1,97 @@ +use super::*; + +/// Evaluate a code snippet, returning a value. +fn parse_eval(code: &str) -> Value { + Interpreter::create() + .interpret(code.into()) + .expect("could not interpret code") +} + +#[test] +fn test_if() { + let result = parse_eval( + r#" +if (42 > 23) + "pass"; +else + "fail"; +"#, + ); + + assert_eq!(Value::Literal(Literal::String("pass".into())), result,); +} + +#[test] +fn test_scope() { + let result = parse_eval( + r#" +var result = ""; + +var a = "global a, "; +var b = "global b, "; +var c = "global c"; + +{ + var a = "outer a, "; + var b = "outer b, "; + + { + var a = "inner a, "; + result = a + b + c; + } +} +"#, + ); + + assert_eq!( + Value::Literal(Literal::String("inner a, outer b, global c".into())), + result, + ); +} + +#[test] +fn test_binary_operators() { + assert_eq!(Value::Literal(Literal::Number(42.0)), parse_eval("40 + 2;")); + + assert_eq!( + Value::Literal(Literal::String("foobar".into())), + parse_eval("\"foo\" + \"bar\";") + ); +} + +#[test] +fn test_functions() { + let result = parse_eval( + r#" +fun add(a, b, c) { + a + b + c; +} + +add(1, 2, 3); +"#, + ); + + assert_eq!(Value::Literal(Literal::Number(6.0)), result); +} + +#[test] +fn test_closure() { + let result = parse_eval( + r#" +fun makeCounter() { + var i = 0; + fun count() { + i = i + 1; + } + + return count; +} + +var counter = makeCounter(); +counter(); // "1". +counter(); // "2". +"#, + ); + + assert_eq!(Value::Literal(Literal::Number(2.0)), result); +} diff --git a/users/tazjin/rlox/src/treewalk/mod.rs b/users/tazjin/rlox/src/treewalk/mod.rs new file mode 100644 index 000000000000..2d82b3320a90 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/mod.rs @@ -0,0 +1,6 @@ +use crate::scanner; + +mod errors; +pub mod interpreter; +mod parser; +mod resolver; diff --git a/users/tazjin/rlox/src/treewalk/parser.rs b/users/tazjin/rlox/src/treewalk/parser.rs new file mode 100644 index 000000000000..003cc34b4665 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/parser.rs @@ -0,0 +1,716 @@ +// This implements the grammar of Lox as described starting in the +// Crafting Interpreters chapter "Representing Code". Note that the +// upstream Java implementation works around Java being bad at value +// classes by writing a code generator for Java. +// +// My Rust implementation skips this step because it's unnecessary, we +// have real types. +use crate::treewalk::errors::{Error, ErrorKind}; +use crate::treewalk::scanner::{Token, TokenKind}; +use std::rc::Rc; + +// AST + +#[derive(Debug)] +pub struct Assign { + pub name: Token, + pub value: Box<Expr>, + pub depth: Option<usize>, +} + +#[derive(Debug)] +pub struct Binary { + pub left: Box<Expr>, + pub operator: Token, + pub right: Box<Expr>, +} + +#[derive(Debug)] +pub struct Logical { + pub left: Box<Expr>, + pub operator: Token, + pub right: Box<Expr>, +} + +#[derive(Debug)] +pub struct Grouping(pub Box<Expr>); + +#[derive(Debug, Clone, PartialEq)] +pub enum Literal { + Boolean(bool), + Number(f64), + String(String), + Nil, +} + +#[derive(Debug)] +pub struct Unary { + pub operator: Token, + pub right: Box<Expr>, +} + +#[derive(Debug)] +pub struct Call { + pub callee: Box<Expr>, + pub paren: Token, + pub args: Vec<Expr>, +} + +// Not to be confused with `Var`, which is for assignment. +#[derive(Debug)] +pub struct Variable { + pub name: Token, + pub depth: Option<usize>, +} + +#[derive(Debug)] +pub enum Expr { + Assign(Assign), + Binary(Binary), + Grouping(Grouping), + Literal(Literal), + Unary(Unary), + Call(Call), + Variable(Variable), + Logical(Logical), +} + +// Variable assignment. Not to be confused with `Variable`, which is +// for access. +#[derive(Debug)] +pub struct Var { + pub name: Token, + pub initialiser: Option<Expr>, +} + +#[derive(Debug)] +pub struct Return { + pub value: Expr, +} + +#[derive(Debug)] +pub struct If { + pub condition: Expr, + pub then_branch: Box<Statement>, + pub else_branch: Option<Box<Statement>>, +} + +#[derive(Debug)] +pub struct While { + pub condition: Expr, + pub body: Box<Statement>, +} + +pub type Block = Vec<Statement>; + +#[derive(Debug)] +pub struct Function { + pub name: Token, + pub params: Vec<Token>, + pub body: Block, +} + +#[derive(Debug)] +pub enum Statement { + Expr(Expr), + Print(Expr), + Var(Var), + Block(Block), + If(If), + While(While), + Function(Rc<Function>), + Return(Return), +} + +// Parser + +/* +program โ declaration* EOF ; + +declaration โ funDecl + | varDecl + | statement ; + +funDecl โ "fun" function ; +function โ IDENTIFIER "(" parameters? ")" block ; +parameters โ IDENTIFIER ( "," IDENTIFIER )* ; + + +statement โ exprStmt + | forStmt + | ifStmt + | printStmt + | returnStmt + | whileStmt + | block ; + +forStmt โ "for" "(" ( varDecl | exprStmt | ";" ) + expression? ";" + expression? ")" statement ; + +returnStmt โ "return" expression? ";" ; + +whileStmt โ "while" "(" expression ")" statement ; + +exprStmt โ expression ";" ; + +ifStmt โ "if" "(" expression ")" statement + ( "else" statement )? ; + +printStmt โ "print" expression ";" ; + +expression โ assignment ; +assignment โ IDENTIFIER "=" assignment + | logic_or ; +logic_or โ logic_and ( "or" logic_and )* ; +logic_and โ equality ( "and" equality )* ; +equality โ comparison ( ( "!=" | "==" ) comparison )* ; +comparison โ term ( ( ">" | ">=" | "<" | "<=" ) term )* ; +term โ factor ( ( "-" | "+" ) factor )* ; +factor โ unary ( ( "/" | "*" ) unary )* ; +unary โ ( "!" | "-" ) unary | call ; +call โ primary ( "(" arguments? ")" )* ; +arguments โ expression ( "," expression )* ; +primary โ NUMBER | STRING | "true" | "false" | "nil" + | "(" expression ")" ; +*/ + +struct Parser { + tokens: Vec<Token>, + current: usize, +} + +type ExprResult = Result<Expr, Error>; +type StmtResult = Result<Statement, Error>; + +impl Parser { + // recursive-descent parser functions + + fn declaration(&mut self) -> StmtResult { + if self.match_token(&TokenKind::Fun) { + return self.function(); + } + + if self.match_token(&TokenKind::Var) { + return self.var_declaration(); + } + + self.statement() + } + + fn function(&mut self) -> StmtResult { + let name = self.identifier("Expected function name.")?; + + self.consume( + &TokenKind::LeftParen, + ErrorKind::ExpectedToken("Expect '(' after function name."), + )?; + + let mut params = vec![]; + + if !self.check_token(&TokenKind::RightParen) { + loop { + if params.len() >= 255 { + return Err(Error { + line: self.peek().line, + kind: ErrorKind::InternalError( + "255 parameter limit exceeded.".into(), + ), + }); + } + + params.push(self.identifier("Expected parameter name.")?); + + if !self.match_token(&TokenKind::Comma) { + break; + } + } + } + + self.consume( + &TokenKind::RightParen, + ErrorKind::ExpectedToken("Expect ')' after parameters."), + )?; + + self.consume( + &TokenKind::LeftBrace, + ErrorKind::ExpectedToken("Expect '{' before function body."), + )?; + + Ok(Statement::Function(Rc::new(Function { + name, + params, + body: self.block_statement()?, + }))) + } + + fn var_declaration(&mut self) -> StmtResult { + // Since `TokenKind::Identifier` carries data, we can't use + // `consume`. + let mut var = Var { + name: self.identifier("Expected variable name.")?, + initialiser: None, + }; + + if self.match_token(&TokenKind::Equal) { + var.initialiser = Some(self.expression()?); + } + + self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?; + Ok(Statement::Var(var)) + } + + fn statement(&mut self) -> StmtResult { + if self.match_token(&TokenKind::Print) { + self.print_statement() + } else if self.match_token(&TokenKind::LeftBrace) { + Ok(Statement::Block(self.block_statement()?)) + } else if self.match_token(&TokenKind::If) { + self.if_statement() + } else if self.match_token(&TokenKind::While) { + self.while_statement() + } else if self.match_token(&TokenKind::For) { + self.for_statement() + } else if self.match_token(&TokenKind::Return) { + self.return_statement() + } else { + self.expr_statement() + } + } + + fn print_statement(&mut self) -> StmtResult { + let expr = self.expression()?; + self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?; + Ok(Statement::Print(expr)) + } + + fn block_statement(&mut self) -> Result<Block, Error> { + let mut block: Block = vec![]; + + while !self.check_token(&TokenKind::RightBrace) && !self.is_at_end() { + block.push(self.declaration()?); + } + + self.consume(&TokenKind::RightBrace, ErrorKind::ExpectedClosingBrace)?; + + Ok(block) + } + + fn if_statement(&mut self) -> StmtResult { + self.consume( + &TokenKind::LeftParen, + ErrorKind::ExpectedToken("Expected '(' after 'if'"), + )?; + let condition = self.expression()?; + self.consume( + &TokenKind::RightParen, + ErrorKind::ExpectedToken("Expected ')' after condition"), + )?; + + let then_branch = Box::new(self.statement()?); + + let mut stmt = If { + condition, + then_branch, + else_branch: Option::None, + }; + + if self.match_token(&TokenKind::Else) { + stmt.else_branch = Some(Box::new(self.statement()?)); + } + + Ok(Statement::If(stmt)) + } + + fn while_statement(&mut self) -> StmtResult { + self.consume( + &TokenKind::LeftParen, + ErrorKind::ExpectedToken("Expected '(' after 'while'"), + )?; + + let condition = self.expression()?; + + self.consume( + &TokenKind::RightParen, + ErrorKind::ExpectedToken("Expected ')' after 'while'"), + )?; + + Ok(Statement::While(While { + condition, + body: Box::new(self.statement()?), + })) + } + + fn for_statement(&mut self) -> StmtResult { + // Parsing of clauses ... + self.consume( + &TokenKind::LeftParen, + ErrorKind::ExpectedToken("Expected '(' after 'for'"), + )?; + + let initialiser = if self.match_token(&TokenKind::Semicolon) { + None + } else if self.match_token(&TokenKind::Var) { + Some(self.var_declaration()?) + } else { + Some(self.expr_statement()?) + }; + + let condition = if self.check_token(&TokenKind::Semicolon) { + // unspecified condition => infinite loop + Expr::Literal(Literal::Boolean(true)) + } else { + self.expression()? + }; + + self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?; + + let increment = if self.check_token(&TokenKind::RightParen) { + None + } else { + Some(self.expression()?) + }; + + self.consume( + &TokenKind::RightParen, + ErrorKind::ExpectedToken("Expected ')' after for clauses"), + )?; + + let mut body = self.statement()?; + + // ... desugaring to while + + if let Some(inc) = increment { + body = Statement::Block(vec![body, Statement::Expr(inc)]); + } + + body = Statement::While(While { + condition, + body: Box::new(body), + }); + + if let Some(init) = initialiser { + body = Statement::Block(vec![init, body]); + } + + Ok(body) + } + + fn return_statement(&mut self) -> StmtResult { + let value = self.expression()?; + self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?; + Ok(Statement::Return(Return { value })) + } + + fn expr_statement(&mut self) -> StmtResult { + let expr = self.expression()?; + self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?; + Ok(Statement::Expr(expr)) + } + + fn expression(&mut self) -> ExprResult { + self.assignment() + } + + fn assignment(&mut self) -> ExprResult { + let expr = self.logic_or()?; + + if self.match_token(&TokenKind::Equal) { + let equals = self.previous().clone(); + let value = self.assignment()?; + + if let Expr::Variable(Variable { name, .. }) = expr { + return Ok(Expr::Assign(Assign { + name, + value: Box::new(value), + depth: None, + })); + } + + return Err(Error { + line: equals.line, + kind: ErrorKind::InvalidAssignmentTarget(format!( + "{:?}", + equals + )), + }); + } + + Ok(expr) + } + + fn logic_or(&mut self) -> ExprResult { + let mut expr = self.logic_and()?; + + while self.match_token(&TokenKind::Or) { + expr = Expr::Logical(Logical { + left: Box::new(expr), + operator: self.previous().clone(), + right: Box::new(self.logic_and()?), + }) + } + + Ok(expr) + } + + fn logic_and(&mut self) -> ExprResult { + let mut expr = self.equality()?; + + while self.match_token(&TokenKind::And) { + expr = Expr::Logical(Logical { + left: Box::new(expr), + operator: self.previous().clone(), + right: Box::new(self.equality()?), + }) + } + + Ok(expr) + } + + fn equality(&mut self) -> ExprResult { + self.binary_operator( + &[TokenKind::BangEqual, TokenKind::EqualEqual], + Self::comparison, + ) + } + + fn comparison(&mut self) -> ExprResult { + self.binary_operator( + &[ + TokenKind::Greater, + TokenKind::GreaterEqual, + TokenKind::Less, + TokenKind::LessEqual, + ], + Self::term, + ) + } + + fn term(&mut self) -> ExprResult { + self.binary_operator(&[TokenKind::Minus, TokenKind::Plus], Self::factor) + } + + fn factor(&mut self) -> ExprResult { + self.binary_operator(&[TokenKind::Slash, TokenKind::Star], Self::unary) + } + + fn unary(&mut self) -> ExprResult { + if self.match_token(&TokenKind::Bang) + || self.match_token(&TokenKind::Minus) + { + return Ok(Expr::Unary(Unary { + operator: self.previous().clone(), + right: Box::new(self.unary()?), + })); + } + + return self.call(); + } + + fn call(&mut self) -> ExprResult { + let mut expr = self.primary()?; + + loop { + if self.match_token(&TokenKind::LeftParen) { + expr = self.finish_call(expr)?; + } else { + break; + } + } + + Ok(expr) + } + + fn finish_call(&mut self, callee: Expr) -> ExprResult { + let mut args = vec![]; + + if !self.check_token(&TokenKind::RightParen) { + loop { + // TODO(tazjin): Check for max args count + args.push(self.expression()?); + if !self.match_token(&TokenKind::Comma) { + break; + } + } + } + + let paren = self.consume( + &TokenKind::RightParen, + ErrorKind::ExpectedToken("Expect ')' after arguments."), + )?; + + Ok(Expr::Call(Call { + args, + callee: Box::new(callee), + paren, + })) + } + + fn primary(&mut self) -> ExprResult { + let next = self.advance(); + let literal = match next.kind { + TokenKind::True => Literal::Boolean(true), + TokenKind::False => Literal::Boolean(false), + TokenKind::Nil => Literal::Nil, + TokenKind::Number(num) => Literal::Number(num), + TokenKind::String(string) => Literal::String(string), + + TokenKind::LeftParen => { + let expr = self.expression()?; + self.consume( + &TokenKind::RightParen, + ErrorKind::UnmatchedParens, + )?; + return Ok(Expr::Grouping(Grouping(Box::new(expr)))); + } + + TokenKind::Identifier(_) => { + return Ok(Expr::Variable(Variable { + name: next, + depth: None, + })) + } + + unexpected => { + eprintln!("encountered {:?}", unexpected); + return Err(Error { + line: next.line, + kind: ErrorKind::ExpectedExpression(next.lexeme), + }); + } + }; + + Ok(Expr::Literal(literal)) + } + + // internal helpers + + fn identifier(&mut self, err: &'static str) -> Result<Token, Error> { + if let TokenKind::Identifier(_) = self.peek().kind { + Ok(self.advance()) + } else { + Err(Error { + line: self.peek().line, + kind: ErrorKind::ExpectedToken(err), + }) + } + } + + /// Check if the next token is in `oneof`, and advance if it is. + fn match_token(&mut self, token: &TokenKind) -> bool { + if self.check_token(token) { + self.advance(); + return true; + } + + false + } + + /// Return the next token and advance parser state. + fn advance(&mut self) -> Token { + if !self.is_at_end() { + self.current += 1; + } + + return self.previous().clone(); + } + + fn is_at_end(&self) -> bool { + self.check_token(&TokenKind::Eof) + } + + /// Is the next token `token`? + fn check_token(&self, token: &TokenKind) -> bool { + self.peek().kind == *token + } + + fn peek(&self) -> &Token { + &self.tokens[self.current] + } + + fn previous(&self) -> &Token { + &self.tokens[self.current - 1] + } + + fn consume( + &mut self, + kind: &TokenKind, + err: ErrorKind, + ) -> Result<Token, Error> { + if self.check_token(kind) { + return Ok(self.advance()); + } + + Err(Error { + line: self.peek().line, + kind: err, + }) + } + + fn synchronise(&mut self) { + self.advance(); + + while !self.is_at_end() { + if self.previous().kind == TokenKind::Semicolon { + return; + } + + match self.peek().kind { + TokenKind::Class + | TokenKind::Fun + | TokenKind::Var + | TokenKind::For + | TokenKind::If + | TokenKind::While + | TokenKind::Print + | TokenKind::Return => return, + + _ => { + self.advance(); + } + } + } + } + + fn binary_operator( + &mut self, + oneof: &[TokenKind], + each: fn(&mut Parser) -> ExprResult, + ) -> ExprResult { + let mut expr = each(self)?; + + while oneof.iter().any(|t| self.match_token(t)) { + expr = Expr::Binary(Binary { + left: Box::new(expr), + operator: self.previous().clone(), + right: Box::new(each(self)?), + }) + } + + return Ok(expr); + } +} + +pub fn parse(tokens: Vec<Token>) -> Result<Block, Vec<Error>> { + let mut parser = Parser { tokens, current: 0 }; + let mut program: Block = vec![]; + let mut errors: Vec<Error> = vec![]; + + while !parser.is_at_end() { + match parser.declaration() { + Err(err) => { + errors.push(err); + parser.synchronise(); + } + Ok(decl) => { + program.push(decl); + } + } + } + + if errors.is_empty() { + Ok(program) + } else { + Err(errors) + } +} diff --git a/users/tazjin/rlox/src/treewalk/resolver.rs b/users/tazjin/rlox/src/treewalk/resolver.rs new file mode 100644 index 000000000000..8231ce5a9e58 --- /dev/null +++ b/users/tazjin/rlox/src/treewalk/resolver.rs @@ -0,0 +1,214 @@ +// Resolves variable access to their specific instances in the +// environment chain. +// +// https://craftinginterpreters.com/resolving-and-binding.html + +use std::collections::HashMap; +use std::rc::Rc; + +use crate::treewalk::errors::{Error, ErrorKind}; +use crate::treewalk::parser::{self, Expr, Statement}; +use crate::treewalk::scanner::Token; + +#[derive(Default)] +struct Resolver<'a> { + scopes: Vec<HashMap<&'a str, bool>>, +} + +impl<'a> Resolver<'a> { + // AST traversal + fn resolve(&mut self, program: &'a mut parser::Block) -> Result<(), Error> { + self.begin_scope(); + for stmt in program { + self.resolve_stmt(stmt)?; + } + self.end_scope(); + + Ok(()) + } + + fn resolve_stmt(&mut self, stmt: &'a mut Statement) -> Result<(), Error> { + match stmt { + Statement::Expr(expr) => self.resolve_expr(expr), + Statement::Print(expr) => self.resolve_expr(expr), + Statement::Var(var) => self.resolve_var(var), + Statement::Return(ret) => self.resolve_expr(&mut ret.value), + Statement::Block(block) => self.resolve(block), + + Statement::If(if_stmt) => { + self.resolve_expr(&mut if_stmt.condition)?; + self.resolve_stmt(&mut if_stmt.then_branch)?; + + if let Some(branch) = if_stmt.else_branch.as_mut() { + self.resolve_stmt(branch)?; + } + + Ok(()) + } + + Statement::While(while_stmt) => { + self.resolve_expr(&mut while_stmt.condition)?; + self.resolve_stmt(&mut while_stmt.body) + } + + Statement::Function(func) => match Rc::get_mut(func) { + Some(func) => self.resolve_function(func), + // The resolver does not clone references, so unless + // the interpreter is called before the resolver this + // case should never happen. + None => return Err(Error { + line: 0, + kind: ErrorKind::InternalError( + "multiple function references before interpretation" + .into(), + ), + }), + }, + } + } + + fn resolve_var(&mut self, var: &'a mut parser::Var) -> Result<(), Error> { + self.declare(&var.name.lexeme); + + if let Some(init) = &mut var.initialiser { + self.resolve_expr(init)?; + } + + self.define(&var.name.lexeme); + + Ok(()) + } + + fn resolve_function( + &mut self, + func: &'a mut parser::Function, + ) -> Result<(), Error> { + self.declare(&func.name.lexeme); + self.define(&func.name.lexeme); + + self.begin_scope(); + + for param in &func.params { + self.declare(¶m.lexeme); + self.define(¶m.lexeme); + } + + for stmt in &mut func.body { + self.resolve_stmt(stmt)?; + } + + self.end_scope(); + + Ok(()) + } + + fn resolve_expr(&mut self, expr: &'a mut Expr) -> Result<(), Error> { + match expr { + Expr::Variable(var) => self.resolve_variable(var), + Expr::Assign(assign) => self.resolve_assign(assign), + Expr::Grouping(grouping) => self.resolve_expr(&mut grouping.0), + Expr::Call(call) => self.resolve_call(call), + Expr::Literal(_) => Ok(()), + Expr::Unary(unary) => self.resolve_expr(&mut unary.right), + + Expr::Logical(log) => { + self.resolve_expr(&mut log.left)?; + self.resolve_expr(&mut log.right) + } + + Expr::Binary(binary) => { + self.resolve_expr(&mut binary.left)?; + self.resolve_expr(&mut binary.right) + } + } + } + + fn resolve_variable( + &mut self, + var: &'a mut parser::Variable, + ) -> Result<(), Error> { + if let Some(scope) = self.scopes.last_mut() { + if let Some(false) = scope.get(var.name.lexeme.as_str()) { + return Err(Error { + line: var.name.line, + kind: ErrorKind::StaticError( + "can't read local variable in its own initialiser" + .into(), + ), + }); + } + } + + var.depth = self.resolve_local(&var.name); + Ok(()) + } + + fn resolve_assign( + &mut self, + assign: &'a mut parser::Assign, + ) -> Result<(), Error> { + self.resolve_expr(&mut assign.value)?; + assign.depth = self.resolve_local(&assign.name); + Ok(()) + } + + fn resolve_local(&mut self, name: &'a Token) -> Option<usize> { + for (c, scope) in self.scopes.iter().rev().enumerate() { + if scope.contains_key(name.lexeme.as_str()) { + return Some(c); + } + } + + None + } + + fn resolve_call( + &mut self, + call: &'a mut parser::Call, + ) -> Result<(), Error> { + self.resolve_expr(&mut call.callee)?; + + for arg in call.args.iter_mut() { + self.resolve_expr(arg)?; + } + + Ok(()) + } + + // Internal helpers + + fn declare(&mut self, name: &'a str) { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(&name, false); + } + } + + fn define(&mut self, name: &'a str) { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(&name, true); + } + } + + fn begin_scope(&mut self) { + self.scopes.push(Default::default()); + } + + fn end_scope(&mut self) { + self.scopes.pop(); + } +} + +pub fn resolve( + globals: &[String], + block: &mut parser::Block, +) -> Result<(), Error> { + let mut resolver: Resolver = Default::default(); + + // Scope for static globals only starts, never ends. + resolver.begin_scope(); + for global in globals { + resolver.define(global); + } + + resolver.resolve(block) +} diff --git a/users/tazjin/russian/helpers.el b/users/tazjin/russian/helpers.el new file mode 100644 index 000000000000..41d4aa34f47e --- /dev/null +++ b/users/tazjin/russian/helpers.el @@ -0,0 +1,7 @@ +;; Helper functions for creating the other files. + +(defun wiktionary-lookup-at-point (ask-lang) + (interactive "P") + (let ((language (if ask-lang (read-string "Language code? ") "ru"))) + (eww (concat "https://ru.wiktionary.org/wiki/" + (thing-at-point 'word))))) diff --git a/users/tazjin/russian/roots.el b/users/tazjin/russian/roots.el new file mode 100644 index 000000000000..77d09b47261c --- /dev/null +++ b/users/tazjin/russian/roots.el @@ -0,0 +1,28 @@ +;; '(root explanation) +;; +;; All roots without explanations are TODOs. +;; +;; In some cases, roots are not direct morphological roots of their +;; descendent words (e.g. -ะณะพะปะพะฒ- => ะณะปะฐะฒะฝัะน) + +'(("-ะฒะตัั-" "everything, all, every, etc.") + ("-ะฒะธะด-" "seeing, viewing etc.") + ("-ะฒัะตะผ-" "time") + ("-ะณะพะฒะพั-" "related to talking") + ("-ะณะพะปะพะฒ-" "head, main, etc.") + ("-ะดััะณ-" nil) + ("-ะดัะผ-" "thinking, thoughts") + ("-ะถะธ-" "life") + ("-ะทะฝะฐ-" "knowing, knowledge") + ("-ะธะผั-" "name") + ("-ะน-" "walking, moving to") + ("-ะผะพัั-" "ability, permission") + ("-ะฝะพะฒ-" "new") + ("-ะพะฑั-" "common?") + ("-ะฟัะฐะฒะด-" "truth") + ("-ะฟัะพั-" "question") + ("-ัะบะฐะท-" nil) + ("-ัะผะพัั-" "watching, viewing") + ("-ัััะฐะฝ-" "country?") + ("-ั ะพะด-" "movement") + ("-ั ะพัะพั-" "goodness, niceness")) diff --git a/users/tazjin/russian/russian.el b/users/tazjin/russian/russian.el new file mode 100644 index 000000000000..28f1addeaa04 --- /dev/null +++ b/users/tazjin/russian/russian.el @@ -0,0 +1,97 @@ +(require 'cl-macs) +(require 'ht) +(require 'seq) +(require 's) + +;; Type definitions for Russian structures + +(cl-defstruct russian-word + "Definition and metadata of a single Russian word." + (word nil :type string) + (translations :type list + :documentation "List of lists of strings, each a set of translations.") + + (notes nil :type list ;; of string + :documentation "free-form notes about this word") + + (roots nil :type list ;; of string + :documentation "list of strings that correspond with roots (exact string match)")) + +(defun russian--merge-words (previous new) + "Merge two Russian word definitions together. If no previous + definition exists, only the new one will be returned." + (if (not previous) new + (cl-assert (equal (russian-word-word previous) + (russian-word-word new)) + "different words passed into merge function") + (make-russian-word :word (russian-word-word previous) + :translations (-concat (russian-word-translations previous) + (russian-word-translations new)) + :notes (-concat (russian-word-notes previous) + (russian-word-notes new)) + :roots (-concat (russian-word-roots previous) + (russian-word-roots new))))) + +;; Definitions for creating a data structure of all Russian words. + +(defvar russian-words (make-hash-table) + "Table of all Russian words in the corpus.") + +(defun russian--define-word (word) + "Define a single word in the corpus, optionally merging it with + another entry." + (let ((key (russian-word-word word))) + (ht-set russian-words key (russian--merge-words + (ht-get russian-words key) + word)))) + +(defmacro define-russian-words (&rest words) + "Define the list of all available words. There may be more than + one entry for a word in some cases." + (declare (indent defun)) + + ;; Clear the table before proceeding with insertion + (setq russian-words (make-hash-table)) + + (seq-map + (lambda (word) + (russian--define-word (make-russian-word :word (car word) + :translations (cadr word) + :notes (caddr word) + :roots (cadddr word)))) + words) + + '(message "Defined %s unique words." (ht-size russian-words))) + +;; Helpers to train Russian words through passively. + +(defun russian--format-word (word) + "Format a Russian word suitable for echo display." + (apply #'s-concat + (-flatten + (list (russian-word-word word) + " - " + (s-join ", " (russian-word-translations word)) + (when-let ((roots (russian-word-roots word))) + (list " [" (s-join ", " roots) "]")) + (when-let ((notes (russian-word-notes word))) + (list " (" (s-join "; " notes) ")")))))) + +(defun display-russian-words () + "Convert Russian words to passively terms and start passively." + (interactive) + (setq passively-learn-terms (make-hash-table)) + (ht-map + (lambda (k v) + (ht-set passively-learn-terms k (russian--format-word v))) + russian-words) + (passively-enable)) + +(defun lookup-last-russian-word (in-eww) + "Look up the last Russian word in Wiktionary" + (interactive "P") + (let ((url (concat "https://ru.wiktionary.org/wiki/" passively-last-displayed))) + (if in-eww (eww url) + (browse-url url)))) + +(provide 'russian) diff --git a/users/tazjin/russian/words.el b/users/tazjin/russian/words.el new file mode 100644 index 000000000000..784e5bddde5e --- /dev/null +++ b/users/tazjin/russian/words.el @@ -0,0 +1,723 @@ +;; entries :: '(entry ...)' +;; entry :: '(word translations note roots) +;; note :: (or nil string) +;; translations :: '(translation ...) +;; roots :: '(root ...) + +(require 'russian) + +(define-russian-words + ;; 1-50 + ("ะธ" ("and" "though")) + ("ะฒ" ("in" "at")) + ("ะฝะต" ("not")) + ("ะพะฝ" ("he")) + ("ะฝะฐ" ("on" "it" "at" "to")) + ("ั" ("I")) + ("ััะพ" ("what" "that" "why")) + ("ัะพั" ("that")) + ("ะฑััั" ("to be")) + ("ั" ("with" "and" "from" "of")) + ("ะฐ" ("while" "and" "but")) + ("ะฒะตัั" ("all" "everything") nil ("-ะฒะตัั-")) + ("ััะพ" ("that" "this" "it")) + ("ะบะฐะบ" ("how" "what" "as" "like")) + ("ะพะฝะฐ" ("she")) + ("ะฟะพ" ("on" "along" "by")) + ("ะฝะพ" ("but")) + ("ะพะฝะธ" ("they")) + ("ะบ" ("to" "for" "by")) + ("ั" ("by" "with" "of")) + ("ัั" ("you")) + ("ะธะท" ("from" "of" "in")) + ("ะผั" ("we")) + ("ะทะฐ" ("behind" "over" "at" "after")) + ("ะฒั" ("you")) + ("ัะฐะบ" ("so" "thus" "then")) + ("ะถะต" ("and" "as for" "but" "same")) + ("ะพั" ("from" "of" "for")) + ("ัะบะฐะทะฐัั" ("to say" "to speak") nil ("-ัะบะฐะท-")) + ("ััะพั" ("this")) + ("ะบะพัะพััะน" ("which" "who" "that")) + ("ะผะพัั" ("be able" "can") nil ("-ะผะพัั-")) + ("ัะตะปะพะฒะตะบ" ("man" "person")) + ("ะพ" ("of" "about" "against")) + ("ะพะดะธะฝ" ("one" "some" "alone")) + ("ะตัั" ("still" "yet")) + ("ะฑั" ("would")) + ("ัะฐะบะพะน" ("such" "so" "some")) + ("ัะพะปัะบะพ" ("only" "merely" "but")) + ("ัะตะฑั" ("myself" "himself" "herself")) + ("ัะฒะพั" ("one's own" "my" "our")) + ("ะบะฐะบะพะน" ("what" "which" "how")) + ("ะบะพะณะดะฐ" ("when" "while" "as")) + ("ัะถะต" ("already" "by now")) + ("ะดะปั" ("for" "to")) + ("ะฒะพั" ("here" "there" "this is" "that's") + ("calling attention to something")) + ("ะบัะพ" ("who" "that" "some")) + ("ะดะฐ" ("yes" "but") ("affirmation (..., right?)")) + ("ะณะพะฒะพัะธัั" ("to say" "to tell" "to speak") nil ("-ะณะพะฒะพั-")) + ("ะณะพะด" ("year")) + + ;; 51 - 100 + ("ะทะฝะฐัั" ("to know" "be aware") nil ("-ะทะฝะฐ-")) + ("ะผะพะน" ("my" "mine")) + ("ะดะพ" ("to" "up to" "about" "before")) + ("ะธะปะธ" ("or")) + ("ะตัะปะธ" ("if")) + ("ะฒัะตะผั" ("time" "season") nil ("-ะฒัะตะผ-")) + ("ััะบะฐ" ("hand" "arm")) + ("ะฝะตั" ("no" "not" "but")) + ("ัะฐะผัะน" ("most" "the very" "the same")) + ("ะฝะธ" ("not a" "not" "neither ... nor")) + ("ััะฐัั" ("to become" "begin" "come")) + ("ะฑะพะปััะพะน" ("big" "large" "important")) + ("ะดะฐะถะต" ("even")) + ("ะดััะณะพะน" ("other" "another" "different") nil ("-ะดััะณ-")) + ("ะฝะฐั" ("our" "ours")) + ("ัะฒะพะน" ("one's own")) + ("ะฝั" ("now" "right" "well" "come on")) + ("ะฟะพะด" ("under" "for" "towards" "to")) + ("ะณะดะต" ("where")) + ("ะดะตะปะพ" ("business" "affair" "matter")) + ("ะตััั" ("to eat" "to be")) + ("ัะฐะผ" ("oneself")) + ("ัะฐะท" ("time" "once" "since")) + ("ััะพะฑั" ("that" "in order that")) + ("ะดะฒะฐ" ("two")) + ("ัะฐะผ" ("there" "then")) + ("ัะตะผ" ("than" "instead of") + ("ัะตะผ ..., ัะตะผ ...")) + ("ะณะปะฐะท" ("eye" "sight")) + ("ะถะธะทะฝั" ("life") nil ("-ะถะธ-")) + ("ะฟะตัะฒัะน" ("first" "front" "former")) + ("ะดะตะฝั" ("day")) + ("ััั" ("here" "now" "then")) + ("ะฒะพ" ("in" "at") + ("as particle also: wow, exactly, ...")) + ("ะฝะธััะพ" ("nothing")) + ("ะฟะพัะพะผ" ("afterwards" "then")) + ("ะพัะตะฝั" ("very")) + ("ัะพ" ("with")) + ("ั ะพัะตัั" ("to want")) + ("ะปะธ" ("whether" "if")) + ("ะฟัะธ" ("attached to" "in the presence of" "by" "about")) + ("ะณะพะปะพะฒะฐ" ("head" "mind" "brains") nil ("-ะณะพะปะพะฒ-")) + ("ะฝะฐะดะพ" ("over" "above" "ought to")) + ("ะฑะตะท" ("without")) + ("ะฒะธะดะตัั" ("to see") nil ("-ะฒะธะด-")) + ("ะธะดัะธ" ("to go" "to come")) + ("ัะตะฟะตัั" ("now" "nowadays")) + ("ัะพะถะต" ("also" "as well" "too")) + ("ััะพััั" ("to stand" "be" "stand up")) + ("ะดััะณ" ("friend")) + ("ะดะพะผ" ("house" "home")) + + ;; 101-150 + ("ัะตะนัะฐั" ("now" "presently" "soon")) + ("ะผะพะถะฝะพ" ("possible" "permitted") nil ("-ะผะพัั-")) + ("ะฟะพัะปะต" ("after" "afterwards")) + ("ัะปะพะฒะพ" ("word")) + ("ะทะดะตัั" ("here")) + ("ะดัะผะฐัั" ("to think" "to believe") nil ("-ะดัะผ-")) + ("ะผะตััะพ" ("place" "seat")) + ("ัะฟัะพัะธัั" ("to ask") nil ("-ะฟัะพั-")) + ("ัะตัะตะท" ("through" "across")) + ("ะปะธัะพ" ("face" "person")) + ("ััะพ" ("what" "which" "that")) + ("ัะพะณะดะฐ" ("then")) + ("ั ะพัะพัะธะน" ("good" "nice") nil ("-ั ะพัะพั-")) + ("ะบะฐะถะดัะน" ("every" "each")) + ("ะฝะพะฒัะน" ("new" "modern") nil ("-ะฝะพะฒ-")) + ("ะถะธัั" ("to live") nil ("-ะถะธ-")) + ("ะดะพะปะถะฝัะน" ("due" "proper" "should")) + ("ัะผะพััะตัั" ("to look" "watch")) + ("ะฟะพัะตะผั" ("why")) + ("ะฟะพัะพะผั" ("that's why")) + ("ััะพัะพะฝะฐ" ("side" "party")) + ("ะฟัะพััะพ" ("simply")) + ("ะฝะพะณะฐ" ("foot" "leg")) + ("ัะธะดะตัั" ("to sit")) + ("ะฟะพะฝััั" ("to understand" "to realise")) + ("ะธะผะตัั" ("to own" "to have")) + ("ะบะพะฝะตัะฝัะน" ("final" "last")) + ("ะดะตะปะฐัั" ("to do" "make")) + ("ะฒะดััะณ" ("suddenly")) + ("ะฝะฐะด" ("above" "over")) + ("ะฒะทััั" ("to take")) + ("ะฝะธะบัะพ" ("nobody")) + ("ะฟะพะฝะธะผะฐัั" ("to understand")) + ("ะบะฐะทะฐัััั" ("to seem" "to appear")) + ("ัะฐะฑะพัะฐ" ("work" "job")) + ("ััะธ" ("three")) + ("ะฒะฐั" ("yours")) + ("ัะถ" ("really" "already")) + ("ะทะตะผะปั" ("earth" "land" "soil")) + ("ะบะพะฝะตั" ("end" "distance")) + ("ะฝะตัะบะพะปัะบะพ" ("several" "some")) + ("ัะฐั" ("hour" "time")) + ("ะณะพะปะพั" ("voice")) + ("ะณะพัะพะด" ("town" "city")) + ("ะฟะพัะปะตะดะฝะธะน" ("last" "the latest" "new")) + + ;; 151-200 + ("ะฟะพะบะฐ" ("for the present")) ;; TODO(tazjin): review + ("ั ะพัะพัะพ" ("well") nil ("-ั ะพัะพั-")) + ("ะดะฐะฒะฐัั" ("to give" "to grant")) + ("ะฒะพะดะฐ" ("water")) + ("ะฑะพะปะตะต" ("more")) + ("ั ะพัั" ("although")) + ("ะฒัะตะณะดะฐ" ("always")) + ("ะฒัะพัะพะน" ("second")) + ("ะบัะดะฐ" ("where" "what for" "much")) + ("ะฟะพะนัะธ" ("to go") nil ("-ะน-")) + ("ััะพะป" ("table" "desk" "board")) + ("ัะตะฑัะฝะพะบ" ("child" "kid" "infant")) + ("ัะฒะธะดะตัั" ("to see")) + ("ัะธะปะฐ" ("strength" "force")) + ("ะพัะตั" ("father")) + ("ะถะตะฝัะธะฝะฐ" ("woman")) + ("ะผะฐัะธะฝะฐ" ("car" "machine" "engine")) + ("ัะปััะฐะน" ("case" "occasion" "incident")) + ("ะฝะพัั" ("night")) + ("ััะฐะทั" ("at once" "right away" "just")) + ("ะผะธั" ("world" "peace")) + ("ัะพะฒัะตะผ" ("quite" "entirely" "totally")) + ("ะพััะฐัััั" ("to remain" "to stay")) + ("ะพะฑ" ("about" "of")) + ("ะฒะธะด" ("appearance" "look" "view")) + ("ะฒัะนัะธ" ("to go out" "to exit" "to come out" "to appear") nil ("-ะน-")) + ("ะดะฐัั" ("to give")) + ("ัะฐะฑะพัะฐัั" ("to work")) + ("ะปัะฑะธัั" ("to work")) + ("ััะฐััะน" ("old")) + ("ะฟะพััะธ" ("almost")) + ("ััะด" ("row" "line")) + ("ะพะบะฐะทะฐัััั" ("find oneself" "turn out")) + ("ะฝะฐัะฐะปะพ" ("beginning" "origin" "source")) + ("ัะฒะพะน" ("your" "yours")) + ("ะฒะพะฟัะพั" ("question" "matter" "problem") nil ("-ะฟัะพั-")) + ("ะผะฝะพะณะพ" ("many" "much")) + ("ะฒะพะนะฝะฐ" ("war")) + ("ัะฝะพะฒะฐ" ("again")) + ("ะพัะฒะตัะธัั" ("to answer" "to reply")) + ("ะผะตะถะดั" ("between" "among")) + ("ะฟะพะดัะผะฐัั" ("to think")) + ("ะพะฟััั" ("again")) + ("ะฑะตะปัะน" ("white")) + ("ะดะตะฝัะณะธ" ("money")) + ("ะทะฝะฐัะธัั" ("to mean" "to signify") nil ("-ะทะฝะฐ-")) + ("ะฟัะพ" ("about" "for")) + ("ะปะธัั" ("only" "as soon as")) + ("ะผะธะฝััะฐ" ("minute" "moment")) + ("ะถะตะฝะฐ" ("wife")) + + ;; 201-300 + ("ะฟะพัะผะพััะตัั" ("to watch" "to look" "to inspect") nil ("-ัะผะพัั-")) + ("ะฟัะฐะฒะดะฐ" ("truth") nil ("-ะฟัะฐะฒะด-")) + ("ะณะปะฐะฒะฝัะน" ("main" "chief") nil ("-ะณะพะปะพะฒ-")) + ("ัััะฐะฝะฐ" ("country") nil ("-ัััะฐะฝ-")) + ("ัะฒะตั" ("light" "world")) + ("ะถะดะฐัั" ("to wait")) + ("ะผะฐัั" ("mother")) + ("ะฑัะดัะพ" ("as if" "as though")) + ("ะฝะธะบะพะณะดะฐ" ("never")) + ("ัะพะฒะฐัะธั" ("comrade" "friend")) + ("ะดะพัะพะณะฐ" ("road" "way" "journey")) + ("ะพะดะฝะฐะบะพ" ("however" "although")) + ("ะปะตะถะฐัั" ("to lie" "to be situated")) + ("ะธะผะตะฝะฝะพ" ("namely" "just" "exactly") nil ("-ะธะผั-")) + ("ะพะบะฝะพ" ("window")) + ("ะฝะธะบะฐะบะพะน" ("no" "none")) + ("ะฝะฐะนัะธ" ("to find" "to discover") nil ("-ะน-")) + ("ะฟะธัะฐัั" ("to write")) + ("ะบะพะผะฝะฐัะฐ" ("room")) + ("ะะพัะบะฒะฐ" ("Moscow")) + ("ัะฐััั" ("part" "share" "department")) + ("ะฒะพะพะฑัะต" ("in general" "altogether" "on the whole") nil ("-ะพะฑั-")) + ("ะบะฝะธะณะฐ" ("book")) + ("ะผะฐะปะตะฝัะบะธะน" ("small" "little")) + ("ัะปะธัะฐ" ("street")) + ("ัะตะถะธัั" ("to decide" "to solve")) + ("ะดะฐะปะตะบะธะน" ("distant" "remote")) + ("ะดััะฐ" ("soul" "spirit")) + ("ัััั" ("hardly" "slightly")) + ("ะฒะตัะฝััััั" ("to return")) + ("ัััะพ" ("morning")) + ("ะฝะตะบะพัะพััะน" ("some")) + ("ััะธัะฐัั" ("to count" "to consider")) + ("ัะบะพะปัะบะพ" ("how much" "how many")) + ("ะฟะพะผะฝะธัั" ("to remember")) + ("ะฒะตัะตั" ("evening")) + ("ะฟะพะป" ("floor" "gender")) + ("ัะฐะบะธ" ("after all")) + ("ะฟะพะปััะธัั" ("to receive" "to get" "to obtain")) + ("ะฝะฐัะพะด" ("people" "nation")) + ("ะฟะปะตัะพ" ("shoulder" "upper arm")) + ("ั ะพัั" ("even" "if you want" "though")) + ("ัะตะณะพะดะฝั" ("today")) + ("ะฑะพะณ" ("god")) + ("ะฒะผะตััะต" ("together")) + ("ะฒะทะณะปัะด" ("look" "glance" "view")) + ("ั ะพะดะธัั" ("to go" "to walk") nil ("-ั ะพะด-")) + ("ะทะฐัะตะผ" ("what for" "why")) + ("ัะพะฒะตััะบะธะน" ("Soviet")) + ("ััััะบะธะน" ("Russian")) + ("ะฑัะฒะฐัั" ("to be" "to visit" "to happen")) + ("ะฟะพะปะฝัะน" ("full" "complete" "whole")) + ("ะฟัะธะนัะธ" ("to arrive" "to come") nil ("-ะน-")) + ("ะฟะฐะปะตั" ("finger" "toe")) + ("ะ ะพััะธั" ("Russia")) + ("ะปัะฑะพะน" ("any" "every")) + ("ะธััะพัะธั" ("history" "story" "event")) + ("ะฝะฐะบะพะฝะตั" ("finally" "at least")) + ("ะผััะปั" ("thought" "idea")) + ("ัะทะฝะฐัั" ("to know" "to learn" "to recognise") nil ("-ะทะฝะฐ-")) + ("ะฝะฐะทะฐะด" ("back" "backwards" "ago")) + ("ะพะฑัะธะน" ("general" "common") nil ("-ะพะฑั-")) + ("ะทะฐะผะตัะธัั" ("to notice" "to observe")) + ("ัะปะพะฒะฝะพ" ("as if" "like")) + ("ะฟัะพัะปัะน" ("past" "vergangen") nil ("-ะน-")) + ("ัะนัะธ" ("to leave" "to go away") nil ("-ะน-")) + ("ะธะทะฒะตััะฝัะน" ("well-known" "famous")) + ("ะดะฐะฒะฝะพ" ("long ago")) + ("ัะปััะฐัั" ("to hear")) + ("ัะปััะฐัั" ("to listen" "to hear")) + ("ะฑะพััััั" ("to be afraid" "fear")) + ("ััะฝ" ("son")) + ("ะฝะตะปัะทั" ("it is impossible" "can't")) + ("ะฟััะผะพ" ("straight" "frankly")) + ("ะดะพะปะณะพ" ("for a long time")) + ("ะฑััััะพ" ("fast" "quickly")) + ("ะปะตั" ("forest")) + ("ะฟะพั ะพะถะธะน" ("similar" "alike") nil ("-ั ะพะด-")) + ("ะฟะพัะฐ" ("time" "pore")) + ("ะฟััั" ("five")) + ("ะณะปัะดะตัั" ("to look" "to gaze")) + ("ะพะฝะพ" ("it")) + ("ัะตััั" ("to sit")) + ("ะธะผั" ("name") nil ("-ะธะผั-")) + ("ะถ" ("and" "as for" "but")) + ("ัะฐะทะณะพะฒะพั" ("conversation" "talk") nil ("-ะณะพะฒะพั-")) + ("ัะตะปะพ" ("body")) + ("ะผะพะปะพะดะพะน" ("young")) + ("ััะตะฝะฐ" ("wall")) + ("ะบัะฐัะฝัะน" ("red")) + ("ัะธัะฐัั" ("to read")) + ("ะฟัะฐะฒะพ" ("right")) + ("ััะฐัะธะบ" ("old man")) + ("ัะฐะฝะฝะธะน" ("early")) + ("ั ะพัะตัััั" ("to want" "to like")) + ("ะผะฐะผะฐ" ("mummy" "mum")) + ("ะพััะฐะฒะฐัััั" ("to remain" "to stay")) + ("ะฒััะพะบะธะน" ("tall" "high")) + ("ะฟััั" ("way" "track" "path")) + ("ะฟะพััะพะผั" ("therefore")) + + ;; 301-400 + ("ัะพะฒะตััะตะฝะฝะพ" ("absolutely" "quite")) + ("ะบัะพะผะต" ("except" "besides")) + ("ัััััะฐ" ("a thousand")) + ("ะผะตััั" ("month")) + ("ะฑัะฐัั" ("to take" "to hire")) + ("ะฝะฐะฟะธัะฐัั" ("to write")) + ("ัะตะปัะน" ("intact" "whole" "entire")) + ("ะพะณัะพะผะฝัะน" ("huge" "enormous")) + ("ะฝะฐัะธะฝะฐัั" ("to begin")) + ("ัะฟะธะฝะฐ" ("back")) + ("ะฝะฐััะพััะธะน" ("present" "real" "true")) + ("ะฟัััั" ("let's" "though")) + ("ัะทัะบ" ("tongue" "language")) + ("ัะพัะฝะพ" ("exactly")) + ("ััะตะดะธ" ("among")) + ("ััััะฒะพะฒะฐัั" ("to feel")) + ("ัะตัะดัะต" ("heart")) + ("ะฒะตััะธ" ("to lead")) + ("ะธะฝะพะณะดะฐ" ("sometimes")) + ("ะผะฐะปััะธะบ" ("boy")) + ("ััะฟะตัั" ("to be in time" "to be successful")) + ("ะฝะตะฑะพ" ("sky")) + ("ะถะธะฒะพะน" ("living" "lively" "alive")) + ("ัะผะตััั" ("death")) + ("ะฟัะพะดะพะปะถะฐัั" ("to continue")) + ("ะดะตะฒััะบะฐ" ("girl")) + ("ะพะฑัะฐะท" ("shape" "form" "image")) + ("ะบะพ" ("to" "towards" "by")) + ("ะทะฐะฑััั" ("to forget")) + ("ะฒะพะบััะณ" ("around")) + ("ะฟะธััะผะพ" ("letter")) + ("ะฒะปะฐััั" ("power")) + ("ัััะฝัะน" ("black")) + ("ะฟัะพะนัะธ" ("to pass" "go by" "be over") nil ("-ะน-")) + ("ะฟะพัะฒะธัััั" ("to appear" "to show up")) + ("ะฒะพะทะดัั " ("air")) + ("ัะฐะทะฝัะน" ("different")) + ("ะฒัั ะพะดะธัั" ("to go out" "to exit") ("MR says 'to nurse'??") ("-ั ะพะด-")) + ("ะฟัะพัะธัั" ("to ask")) + ("ะฑัะฐั" ("brat")) + ("ัะพะฑััะฒะตะฝะฝัะน" ("one's own")) + ("ะพัะฝะพัะตะฝะธะต" ("relationship" "attitude")) + ("ะทะฐัะตะผ" ("then" "after that")) + ("ะฟััะฐัััั" ("to try")) + ("ะฟะพะบะฐะทะฐัั" ("to show" "to display")) + ("ะฒัะฟะพะผะฝะธัั" ("to remember" "to recall")) + ("ัะธััะตะผะฐ" ("system")) + ("ัะตัััะต" ("four")) + ("ะบะฒะฐััะธัะฐ" ("flat" "apartment")) + ("ะดะตัะถะฐัั" ("to hold" "to keep")) + ("ัะฐะบะถะต" ("also" "as well" "too")) + ("ะปัะฑะพะฒั" ("love")) + ("ัะพะปะดะฐั" ("soldier")) + ("ะพัะบัะดะฐ" ("from where")) + ("ััะพะฑ" ("that" "in order that")) + ("ะฝะฐะทัะฒะฐัั" ("to call" "to name")) + ("ััะตัะธะน" ("third")) + ("ั ะพะทัะธะฝ" ("master" "boss" "host")) + ("ะฒัะพะดะต" ("like" "not unlike")) + ("ัั ะพะดะธัั" ("to leave" "to go away") nil ("-ั ะพะด-")) + ("ะฟะพะดะพะนัะธ" ("to approach" "to come up") nil ("-ะน-")) + ("ะฟะพะดะฝััั" ("to lift" "to raise")) + ("ัะฟัะฐัะธะฒะฐัั" ("to ask" "to inquire")) + ("ะฝะฐัะฐะปัะฝะธะบ" ("chief" "head" "superior")) + ("ะพะฑะฐ" ("both")) + ("ะฑัะพัะธัั" ("to throw")) + ("ัะบะพะปะฐ" ("school")) + ("ะฟะฐัะตะฝั" ("boy" "fellow" "guy")) + ("ะบัะพะฒั" ("blood")) + ("ะดะฒะฐะดัะฐัั" ("twenty")) + ("ัะพะปะฝัะต" ("sun")) + ("ะฝะตะดะตะปั" ("week")) + ("ะฟะพัะปะฐัั" ("to send" "to dispatch")) + ("ะฝะฐั ะพะดะธัััั" ("to be found" "to turn up") nil ("-ั ะพะด-")) + ("ัะตะฑััะฐ" ("guys" "children")) + ("ะฟะพััะฐะฒะธัั" ("to put" "to place" "to set")) + ("ะฒััะฐัั" ("to get up" "to rise" "to stand up")) + ("ะฝะฐะฟัะธะผะตั" ("for example" "for instance")) + ("ัะฐะณ" ("step")) + ("ะผัะถัะธะฝะฐ" ("man" "male")) + ("ัะฐะฒะฝะพ" ("alike" "in like manner")) + ("ะฝะพั" ("nose")) + ("ะผะฐะปะพ" ("little" "few")) + ("ะฒะฝะธะผะฐะฝะธะต" ("attention")) + ("ะบะฐะฟะธัะฐะฝ" ("captain" "master")) + ("ัั ะพ" ("ear")) + ("ััะดะฐ" ("to there")) + ("ััะดะฐ" ("to here")) + ("ะธะณัะฐัั" ("to play")) + ("ัะปะตะดะพะฒะฐัั" ("to follow" "to come next")) + ("ัะฐััะบะฐะทะฐัั" ("to tell" "to narrate")) + ("ะฒะตะปะธะบะธะน" ("great")) + ("ะดะตะนััะฒะธัะตะปัะฝะพ" ("indeed" "really")) + ("ัะปะธัะบะพะผ" ("too much")) + ("ััะถัะปัะน" ("heavy")) + ("ัะฟะฐัั" ("to sleep")) + ("ะพััะฐะฒะธัั" ("to leave" "to abandon")) + ("ะฒะพะนัะธ" ("to enter" "to come in") nil ("-ะน-")) + ("ะดะปะธะฝะฝัะน" ("long")) + + ;; 401 - 500 + ("ััะฒััะฒะพ" ("feeling")) + ("ะธะพะปัะฐัั" ("to keep silence" "make no complaint" "say nothing")) + ("ัะฐััะบะฐะทัะฒะฐัั" ("to tell" "narrate")) + ("ะพัะฒะตัะฐัั" ("to answer" "to reply")) + ("ััะฐะฝะพะฒะธัััั" ("to stand" "to become")) + ("ะพััะฐะฝะพะฒะธัััั" ("to stop")) + ("ะฑะตัะตะณ" ("bank" "shore" "coast")) + ("ัะตะผัั" ("family")) + ("ะธัะบะฐัั" ("to search")) + ("ะณะตะฝะตัะฐะป" ("general")) + ("ะผะพะผะตะฝั" ("moment" "instant")) + ("ะดะตัััั" ("ten")) + ("ะฝะฐัะฐัั" ("to begin")) + ("ัะปะตะดัััะธะน" ("next" "following")) + ("ะปะธัะฝัะน" ("personal")) + ("ัััะด" ("labour" "work")) + ("ะฒะตัะธัั" ("to believe")) + ("ะณััะฟะฟะฐ" ("group")) + ("ะฝะตะผะฝะพะณะพ" ("a little")) + ("ะฒะฟัะพัะตะผ" ("however" "though")) + ("ะฒะธะดะฝะพ" ("evidently" "obviously")) + ("ัะฒะปััััั" ("to appear")) + ("ะผัะถ" ("husband")) + ("ัะฐะทะฒะต" ("really?" "perhaps") ("when pondering something")) + ("ะดะฒะธะถะตะฝะธะต" ("movement" "motion")) + ("ะฟะพััะดะพะบ" ("order")) + ("ะพัะฒะตั" ("answer" "reply")) + ("ัะธั ะพ" ("quietly" "silently") ("also as exclamation")) + ("ะทะฝะฐะบะพะผัะน" ("familiar" "acquainted")) + ("ะณะฐะทะตัะฐ" ("newspaper")) + ("ะฟะพะผะพัั" ("help")) + ("ัะธะปัะฝัะน" ("strong" "powerful")) + ("ัะบะพััะน" ("quick" "fast")) + ("ัะพะฑะฐะบะฐ" ("dog")) + ("ะดะตัะตะฒะพ" ("tree")) + ("ัะฝะตะณ" ("snow")) + ("ัะพะฝ" ("dream")) + ("ัะผััะป" ("sense" "meaning" "purpose") ("making sense" "in the sense")) + ("ัะผะพัั" ("to be able") ("ัะฒ")) + ("ะฟัะพัะธะฒ" ("against" "opposite" "contrary to")) + ("ะฑะตะถะฐัั" ("to run" "to hurry")) + ("ะดะฒะพั" ("yard" "court")) + ("ัะพัะผะฐ" ("form" "shape" "uniform")) + ("ะฟัะพััะพะน" ("simple" "easy" "plain")) + ("ะฟัะธะตั ะฐัั" ("to arrive" "to come")) + ("ะธะฝะพะน" ("different" "other")) + ("ะบัะธัะฐัั" ("to cry" "to shout")) + ("ะฒะพะทะผะพะถะฝะพััั" ("possibility" "opportunity" "chance")) + ("ะพะฑัะตััะฒะพ" ("society")) + ("ะทะตะปัะฝัะน" ("green")) + ("ะณััะดั" ("breast" "chest")) + ("ัะณะพะป" ("corner" "angle")) + ("ะพัะบัััั" ("to open")) + ("ะฟัะพะธัั ะพะดะธัั" ("to happen" "to occur" "to take place")) + ("ะปะฐะดะฝะพ" ("well" "all right" "okay")) + ("ัััะฝัะน" ("black") ("noun (m.): as in 'she wears black'")) + ("ะฒะตะบ" ("century" "age")) + ("ะบะฐัะผะฐะฝ" ("pocket")) + ("ะตั ะฐัั" ("to go" "ride" "drive" "travel")) + ("ะฝะตะผะตั" ("German")) + ("ะฝะฐะฒะตัะฝะพะต" ("probably" "most likely")) + ("ะณัะฑะฐ" ("lip")) + ("ะดัะดั" ("uncle")) + ("ะฟัะธั ะพะดะธัั" ("to come" "to arrive")) + ("ัะฐััะพ" ("often")) + ("ะดะพะผะพะน" ("home") ("as in direction")) + ("ะพะณะพะฝั" ("fire")) + ("ะฟะธัะฐัะตะปั" ("writer")) + ("ะฐัะผะธั" ("army")) + ("ัะพััะพัะฝะธะต" ("state" "condition" "fortune")) + ("ะทัะฑ" ("tooth")) + ("ะพัะตัะตะดั" ("queue" "line" "turn")) + ("ะบะพะน" ("which") ("old-fashioned, literary (in set expressions)")) + ("ะฟะพะดะฝััััั" ("to rise" "to climb")) + ("ะบะฐะผะตะฝั" ("stone")) + ("ะณะพััั" ("guest")) + ("ะฟะพะบะฐะทะฐัััั" ("to appear" "to come in sight")) + ("ะฒะตัะตั" ("window")) + ("ัะพะฑะธัะฐัััั" ("to gather" "to assemble" "to intend") ("TODO: intend??")) + ("ะฟะพะฟะฐััั" ("to hit" "to find oneself") ("to get (in phrases)")) + ("ะฟัะธะฝััั" ("to take" "to admit" "to accept")) + ("ัะฝะฐัะฐะปะฐ" ("at first" "from the beginning")) + ("ะปะธะฑะพ" ("or")) + ("ะฟะพะตั ะฐัั" ("to depart" "to set off")) + ("ััะปััะฐัั" ("to hear")) + ("ัะผะตัั" ("to be able" "know" "can")) + ("ัะปััะธัััั" ("to happen")) + ("ัััะฐะฝะฝัะน" ("strange")) + ("ะตะดะธะฝััะฒะตะฝะฝัะน" ("only" "sole")) + ("ัะพัะฐ" ("company") ("(military)")) + ("ะทะฐะบะพะฝ" ("law" "act" "statute")) + ("ะบะพัะพัะบะธะน" ("short")) + ("ะผะพัะต" ("sea")) + ("ะดะพะฑััะน" ("kind")) + ("ััะผะฝัะน" ("dark")) + ("ะณะพัะฐ" ("mountain" "hill")) + ("ะฒัะฐั" ("doctor")) + ("ะบัะฐะน" ("border, edge" "land, country")) + ("ััะฐัะฐัััั" ("to try" "to endeavour")) + ("ะปัััะธะน" ("better" "best")) + + ;; 501 - 600 + ("ัะตะบะฐ" ("river")) + ("ะฒะพะตะฝะฝัะน" ("military")) + ("ะผะตัะฐ" ("measure" "step")) + ("ัััะฐัะฝัะน" ("terrible" "frightful")) + ("ะฒะฟะพะปะฝะต" ("quite" "fully")) + ("ะทะฒะฐัั" ("to call")) + ("ะฟัะพะธะทะพะนัะธ" ("to happen" "to occur" "take place")) + ("ะฒะฟะตัะตะด" ("forward")) + ("ะผะตะดะปะตะฝะฝะพ" ("slowly")) + ("ะฒะพะทะปะต" ("by" "near" "close by")) + ("ะฝะธะบะฐะบ" ("in no way" "by no means")) + ("ะทะฐะฝะธะผะฐัััั" ("to be occupied" "to engage")) + ("ะดะตะนััะฒะธะต" ("action" "effort")) + ("ะดะพะฒะพะปัะฝะพ" ("enough" "rather")) + ("ะฒะตัั" ("thing")) + ("ะฝะตะพะฑั ะพะดะธะผัะน" ("necessary") ("not possible to go around")) + ("ั ะพะด" ("move")) + ("ะฑะพะปั" ("pain")) + ("ััะดัะฑะฐ" ("fate" "fortune" "destiny")) + ("ะฟัะธัะธะฝะฐ" ("cause" "reason" "motive")) + ("ะฟะพะปะพะถะธัั" ("to lay down" "put down" "place")) + ("ะตะดะฒะฐ" ("hardly" "just" "barely")) + ("ัะตััะฐ" ("line" "boundary" "trait")) + ("ะดะตะฒะพัะบะฐ" ("girl" "little girl")) + ("ะปัะณะบะธะน" ("light" "easy")) + ("ะฒะพะปะพั" ("hair")) + ("ะบัะฟะธัั" ("to buy" "purchase")) + ("ะฝะพะผะตั" ("number" "size" "room" "issue")) + ("ะพัะฝะพะฒะฝะพะน" ("main")) + ("ัะธัะพะบะธะน" ("wide")) + ("ัะผะตัะตัั" ("to die")) + ("ะดะฐะปะตะบะพ" ("far" "far off")) + ("ะฟะปะพั ะพ" ("badly")) + ("ะณะปะฐะฒะฐ" ("head" "chief")) + ("ะบัะฐัะธะฒัะน" ("beautiful")) + ("ัะตััะน" ("grey" "dull")) + ("ะฟะธัั" ("to drink")) + ("ะบะพะผะฐะฝะดะธั" ("commander" "officer")) + ("ะพะฑััะฝะพ" ("usually")) + ("ะฟะฐััะธั" ("party")) + ("ะฟัะพะฑะปะตะผะฐ" ("problem" "issue")) + ("ัััะฐั " ("fear")) + ("ะฟัะพั ะพะดะธัั" ("to pass" "go" "study")) + ("ััะฝะพ" ("clear" "clearly")) + ("ัะฝััั" ("to take away" "take off")) + ("ะฑัะผะฐะณะฐ" ("paper")) + ("ะณะตัะพะน" ("hero")) + ("ะฟะฐัะฐ" ("pair" "couple")) + ("ะณะพััะดะฐัััะฒะพ" ("State")) + ("ะดะตัะตะฒะฝั" ("village")) + ("ัะตัั" ("speech")) + ("ะฝะฐัะฐัััั" ("to begin")) + ("ััะตะดััะฒะพ" ("means" "remedy")) + ("ะฟะพะปะพะถะตะฝะธะต" ("position" "posture" "condition" "state")) + ("ัะฒัะทั" ("tie, bond" "connection, relation")) + ("ะฝะตะฑะพะปััะพะน" ("small" "not great")) + ("ะฟัะตะดััะฐะฒะปััั" ("to present" "introduce" "imagine")) + ("ะทะฐะฒััะฐ" ("tomorrow")) + ("ะพะฑัััะฝะธัั" ("to explain")) + ("ะฟัััะพะน" ("empty" "hollow" "idle")) + ("ะฟัะพะธะทะฝะตััะธ" ("to pronounce" "say" "utter")) + ("ัะตะปะพะฒะตัะตัะบะธะน" ("human")) + ("ะฝัะฐะฒะธัััั" ("to please" "be likeable to")) + ("ะพะดะฝะฐะถะดั" ("once" "one day")) + ("ะผะธะผะพ" ("past" "by")) + ("ะธะฝะฐัะต" ("otherwise" "differently|")) + ("ัััะตััะฒัะพะฒะฐัั" ("to exist" "to be")) + ("ะบะปะฐัั" ("class")) + ("ัะดะฐัััั" ("turn out well" "succeed" "manage")) + ("ัะพะปัััะน" ("thick" "heavy" "fat")) + ("ัะตะปั" ("goal" "object" "target")) + ("ัะบะฒะพะทั" ("through")) + ("ะฟัะธะนัะธัั" ("to fit" "fall" "have to") ("ัะตะฑะต ะฟัะธะดัััั - you have to")) + ("ัะธัััะน" ("clean" "pure")) + ("ะทะฝะฐัั" ("to know")) + ("ะฟัะตะถะฝะธะน" ("former")) + ("ะฟัะพัะตััะพั" ("professor")) + ("ะณะพัะฟะพะดะธะฝ" ("gentleman" "Mr.")) + ("ััะฐัััะต" ("happiness" "luck")) + ("ั ัะดะพะน" ("thin" "skinny")) + ("ะดัั " ("spirit")) + ("ะฟะปะฐะฝ" ("plan")) + ("ััะถะพะน" ("somebody else's" "strange" "foreign")) + ("ะทะฐะป" ("hall")) + ("ะฟัะตะดััะฐะฒะธัั" ("to present" "produce" "introduce")) + ("ะพัะพะฑัะน" ("special")) + ("ะดะธัะตะบัะพั" ("director" "manager")) + ("ะฑัะฒัะธะน" ("former" "ex-")) + ("ะฟะฐะผััั" ("memory")) + ("ะฑะปะธะทะบะธะน" ("near" "similar" "intimate")) + ("ัะตะน" ("this")) + ("ัะตะทัะปััะฐั" ("result" "outcome")) + ("ะฑะพะปัะฝะพะน" ("sick")) + ("ะดะฐะฝะฝัะน" ("given" "present")) + ("ะบััะฐัะธ" ("to the point" "at the same time")) + ("ะฝะฐะทะฒะฐัั" ("to call" "name")) + ("ัะปะตะด" ("track" "footprint")) + ("ัะปัะฑะฐัััั" ("to smile") ("ะฝัะฒ")) + ("ะฑัััะปะบะฐ" ("bottle")) + + ;; 601 - 700 + ("ัััะดะฝะพ" ("with difficulty")) + ("ััะปะพะฒะธะต" ("condition" "term")) + ("ะฟัะตะถะดะต" ("before")) + ("ัะผ" ("mind" "brains" "intellect")) + ("ัะปัะฑะฝััััั" ("to smile")) + ("ะฟัะพัะตัั" ("process")) + ("ะบะฐััะธะฝะฐ" ("picture" "painting")) + ("ะฒะผะตััะพ" ("instead")) + ("ััะฐััะธะน" ("elder" "senior")) + ("ะปะตะณะบะพ" ("easily" "lightly")) + ("ัะตะฝัั" ("center")) + ("ะฟะพะดะพะฑะฝัะน" ("similar" "like")) + ("ะฒะพะทะผะพะถะฝะพ" ("possible") ("as ... as possible")) + ("ะพะบะพะปะพ" ("by" "near")) + ("ัะผะตััััั" ("to laugh")) + ("ััะพ" ("hundred")) + ("ะฑัะดััะตะต" ("future")) + ("ั ะฒะฐัะฐัั" ("to snatch" "to seize" "to suffice") ("ะฝัะฒ")) + ("ัะธัะปะพ" ("number")) + ("ะฒััะบะพะต" ("any" "every")) + ("ััะฑะปั" ("ruble")) + ("ะฟะพััะฒััะฒะพะฒะฐัั" ("to feel") ("ัะฒ")) + ("ะฟัะธะฝะตััะธ" ("to bring")) + ("ะฒะตัะฐ" ("faith" "belief")) + ("ะฒะพะฒัะต" ("quiet" "not ... at all")) + ("ัะดะฐั" ("blow" "stroke")) + ("ัะตะปะตัะพะฝ" ("telephone")) + ("ะบะพะปะตะฝะพ" ("knee")) + ("ัะพะณะปะฐัะธัััั" ("to agree" "to consent")) + ("ะผะฐะปะพ" ("little" "few" "not enough")) + ("ะบะพัะธะดะพั" ("corridor" "passage")) + ("ะผัะถะธะบ" ("man")) + ("ะฟัะฐะฒัะน" ("right")) + ("ะฐะฒัะพั" ("author")) + ("ั ะพะปะพะดะฝัะน" ("cold" "cool")) + ("ั ะฒะฐัะธั" ("to snatch" "to seize" "to suffice") ("ัะฒ")) + ("ะผะฝะพะณะธะต" ("many")) + ("ะฒัััะตัะฐ" ("meeting" "reception")) + ("ะบะฐะฑะธะฝะตั" ("study" "room" "office suite")) + ("ะดะพะบัะผะตะฝั" ("document")) + ("ัะฐะผะพะปัั" ("airplane")) + ("ะฒะฝะธะท" ("down" "downwards")) + ("ะฟัะธะฝะธะผะฐัั" ("to take" "to admit" "to accept")) + ("ะธะณัะฐ" ("game" "play")) + ("ัะฐััะบะฐะท" ("story")) + ("ั ะปะตะฑ" ("bread")) + ("ัะฐะทะฒะธัะธะต" ("development")) + ("ัะฑะธัั" ("to kill")) + ("ัะพะดะฝะพะน" ("own" "native" "dear")) + ("ะพัะบััััะน" ("open")) + ("ะผะตะฝะตะต" ("less")) + ("ะฟัะตะดะปะพะถะธัั" ("to offer" "to propose" "to suggest")) + ("ะถัะปััะน" ("yellow")) + ("ะฟัะธั ะพะดะธัััั" ("to fit" "to fall" "to have to")) + ("ะฒัะฟะธัั" ("to drink")) + ("ะบัะธะบะฝััั" ("to cry" "to shout")) + ("ัััะฑะบะฐ" ("tube" "roll" "pipe")) + ("ะฒัะฐะณ" ("enemy")) + ("ะฟะพะบะฐะทัะฒะฐัั" ("to show" "to display")) + ("ะดะฒะพะต" ("two") ("cardinal number")) + ("ะดะพะบัะพั" ("doctor")) + ("ะปะฐะดะพะฝั" ("palm")) + ("ะฒัะทะฒะฐัั" ("to call" "to send")) + ("ัะฟะพะบะพะนะฝะพ" ("quietly")) + ("ะฟะพะฟัะพัะธัั" ("to ask")) + ("ะฝะฐัะบะฐ" ("science")) + ("ะปะตะนัะตะฝะฐะฝั" ("lieutenant")) + ("ัะปัะถะฑะฐ" ("service" "work")) + ("ะพะบะฐะทัะฒะฐัััั" ("to turn out" "to find oneself")) + ("ะฟัะธะฒะตััะธ" ("to bring")) + ("ัะพัะพะบ" ("forty")) + ("ัััั" ("bill" "account")) + ("ะฒะพะทะฒัะฐัะฐัััั" ("to return")) + ("ะทะพะปะพัะพะน" ("golden")) + ("ะผะตััะฝัะน" ("local")) + ("ะบัั ะฝั" ("kitchen")) + ("ะบััะฟะฝัะน" ("large" "big" "prominent")) + ("ัะตัะตะฝะธะต" ("decision" "conclusion")) + ("ะผะพะปะพะดะฐั" ("bride" "young")) + ("ััะธะดัะฐัั" ("thirty")) + ("ัะพะผะฐะฝ" ("novel" "romance")) + ("ะบะพะผะฟะฐะฝะธั" ("company")) + ("ัะฐัััะน" ("frequent")) + ("ัะพััะธะนัะบะธะน" ("Russian")) + ("ัะฐะฑะพัะธะน" ("worky")) + ("ะฟะพัะตัััั" ("to lose")) + ("ัะตัะตะฝะธะต" ("current")) + ("ัะธะฝะธะน" ("dark blue")) + ("ััะพะปัะบะพ" ("so much" "so many")) + ("ััะฟะปัะน" ("warm")) + ("ะผะตัั" ("metre")) + ("ะดะพััะฐัั" ("to reach" "get" "obtain")) + ("ะถะตะปะตะทะฝัะน" ("ferreous" "iron")) + ("ะธะฝััะธััั" ("institute")) + ("ัะพะพะฑัะธัั" ("to report" "to let know")) + ("ะธะฝัะตัะตั" ("interest")) + ("ะพะฑััะฝัะน" ("usual" "ordinary")) + ("ะฟะพัะฒะปััััั" ("to appear" "to show up")) + ("ัะฟะฐััั" ("to fall"))) + +(provide 'russian-words) diff --git a/users/tazjin/wallpapers/bio_thehost_1920.webp b/users/tazjin/wallpapers/bio_thehost_1920.webp new file mode 100644 index 000000000000..1b904c06fadf --- /dev/null +++ b/users/tazjin/wallpapers/bio_thehost_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/busride2_1920.webp b/users/tazjin/wallpapers/busride2_1920.webp new file mode 100644 index 000000000000..ad6ec446f661 --- /dev/null +++ b/users/tazjin/wallpapers/busride2_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_belltowers_2880.webp b/users/tazjin/wallpapers/by_belltowers_2880.webp new file mode 100644 index 000000000000..f7477f168914 --- /dev/null +++ b/users/tazjin/wallpapers/by_belltowers_2880.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_crossing_2560.webp b/users/tazjin/wallpapers/by_crossing_2560.webp new file mode 100644 index 000000000000..efa263790b45 --- /dev/null +++ b/users/tazjin/wallpapers/by_crossing_2560.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_gathering3_2880.webp b/users/tazjin/wallpapers/by_gathering3_2880.webp new file mode 100644 index 000000000000..e6b83bdcd430 --- /dev/null +++ b/users/tazjin/wallpapers/by_gathering3_2880.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_mainservers1_1920.webp b/users/tazjin/wallpapers/by_mainservers1_1920.webp new file mode 100644 index 000000000000..f88d237e2b91 --- /dev/null +++ b/users/tazjin/wallpapers/by_mainservers1_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_warmachines1_2560.webp b/users/tazjin/wallpapers/by_warmachines1_2560.webp new file mode 100644 index 000000000000..848bf62bd7ed --- /dev/null +++ b/users/tazjin/wallpapers/by_warmachines1_2560.webp Binary files differdiff --git a/users/tazjin/wallpapers/by_warmachines3_1920.webp b/users/tazjin/wallpapers/by_warmachines3_1920.webp new file mode 100644 index 000000000000..6002ad695a1e --- /dev/null +++ b/users/tazjin/wallpapers/by_warmachines3_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/clever-man_2880.webp b/users/tazjin/wallpapers/clever-man_2880.webp new file mode 100644 index 000000000000..eb4d3f1bfa33 --- /dev/null +++ b/users/tazjin/wallpapers/clever-man_2880.webp Binary files differdiff --git a/users/tazjin/wallpapers/december1994_1920.webp b/users/tazjin/wallpapers/december1994_1920.webp new file mode 100644 index 000000000000..d2c4da80187c --- /dev/null +++ b/users/tazjin/wallpapers/december1994_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/flyby_1920.webp b/users/tazjin/wallpapers/flyby_1920.webp new file mode 100644 index 000000000000..8df5b1132e74 --- /dev/null +++ b/users/tazjin/wallpapers/flyby_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/gaussfraktarna_1920_badge.webp b/users/tazjin/wallpapers/gaussfraktarna_1920_badge.webp new file mode 100644 index 000000000000..3274a3a2d21d --- /dev/null +++ b/users/tazjin/wallpapers/gaussfraktarna_1920_badge.webp Binary files differdiff --git a/users/tazjin/wallpapers/kraftahq_1920.webp b/users/tazjin/wallpapers/kraftahq_1920.webp new file mode 100644 index 000000000000..62a6debf476f --- /dev/null +++ b/users/tazjin/wallpapers/kraftahq_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/peripheral2_1920.webp b/users/tazjin/wallpapers/peripheral2_1920.webp new file mode 100644 index 000000000000..e454072ac42a --- /dev/null +++ b/users/tazjin/wallpapers/peripheral2_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/ship14_1920.webp b/users/tazjin/wallpapers/ship14_1920.webp new file mode 100644 index 000000000000..502f5dac903e --- /dev/null +++ b/users/tazjin/wallpapers/ship14_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/shipyard_1920.webp b/users/tazjin/wallpapers/shipyard_1920.webp new file mode 100644 index 000000000000..3d4115305d10 --- /dev/null +++ b/users/tazjin/wallpapers/shipyard_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/specky_1920.webp b/users/tazjin/wallpapers/specky_1920.webp new file mode 100644 index 000000000000..b8246618bebc --- /dev/null +++ b/users/tazjin/wallpapers/specky_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/summerlove2_1920.webp b/users/tazjin/wallpapers/summerlove2_1920.webp new file mode 100644 index 000000000000..d64a1cb867ec --- /dev/null +++ b/users/tazjin/wallpapers/summerlove2_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/t50_1920_badge.webp b/users/tazjin/wallpapers/t50_1920_badge.webp new file mode 100644 index 000000000000..f8cb6107f30c --- /dev/null +++ b/users/tazjin/wallpapers/t50_1920_badge.webp Binary files differdiff --git a/users/tazjin/wallpapers/theflood1_1920.webp b/users/tazjin/wallpapers/theflood1_1920.webp new file mode 100644 index 000000000000..335efb057172 --- /dev/null +++ b/users/tazjin/wallpapers/theflood1_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/thelan_1920.webp b/users/tazjin/wallpapers/thelan_1920.webp new file mode 100644 index 000000000000..55e6c22ad212 --- /dev/null +++ b/users/tazjin/wallpapers/thelan_1920.webp Binary files differdiff --git a/users/tazjin/wallpapers/vadrare_1920_badge.webp b/users/tazjin/wallpapers/vadrare_1920_badge.webp new file mode 100644 index 000000000000..887c891da36c --- /dev/null +++ b/users/tazjin/wallpapers/vadrare_1920_badge.webp Binary files differdiff --git a/users/tvlbot.jpg b/users/tvlbot.jpg new file mode 100644 index 000000000000..f0811418dff5 --- /dev/null +++ b/users/tvlbot.jpg Binary files differdiff --git a/users/wpcarro/.envrc b/users/wpcarro/.envrc new file mode 100644 index 000000000000..23adf2d29d3f --- /dev/null +++ b/users/wpcarro/.envrc @@ -0,0 +1,8 @@ +export BRIEFCASE="$(realpath .)" +# I'm ensuring that $NIX_PATH is mostly empty, so that I only depend on +# <briefcase> for now. +# For more information on the NIX_PATH anti-pattern, see here: +# https://nix.dev/tutorials/towards-reproducibility-pinning-nixpkgs.html#pinning-nixpkgs +export NIX_PATH="briefcase=$BRIEFCASE"; +export DESKTOP="zeno.lon.corp.google.com"; +export LAPTOP="seneca"; diff --git a/users/wpcarro/.gitignore b/users/wpcarro/.gitignore new file mode 100644 index 000000000000..0132f389508d --- /dev/null +++ b/users/wpcarro/.gitignore @@ -0,0 +1,31 @@ +.vim +./configs/secrets +**/*/.emacs.d/quelpa/**/* +**/*/.emacs.d/elpa/**/* +**/*/.emacs.d/emojis +**/*/.emacs.d/auto-save-list/**/* +**/*/.emacs.d/eshell/ +**/*/.emacs.d/var/**/* +**/*/.emacs.d/.cache/**/* +**/*/.emacs.d/request +**/*/.emacs.d/network-security.data +**/*/.emacs.d/smex-items +**/*/.gnupg/random_seed +.netrwhist +Vundle.vim +**/*/.emacs.d/custom.el +**/*/.emacs.d/projectile-bookmarks.eld +**/*/.emacs.d/bookmarks +**/*/transient/history.el +*.hi +*.o +__pycache__ +*.class +node_modules/ +/configs/.config/fish/config.fish +/configs/.config/fish/fish_variables +/website/blog/public/ +/emacs/.emacs.d/tramp +.gitsecret/keys/random_seed +!*.secret +secrets.json diff --git a/users/wpcarro/.gitsecret/keys/pubring.kbx b/users/wpcarro/.gitsecret/keys/pubring.kbx new file mode 100644 index 000000000000..692d5c67b04b --- /dev/null +++ b/users/wpcarro/.gitsecret/keys/pubring.kbx Binary files differdiff --git a/users/wpcarro/.gitsecret/keys/pubring.kbx~ b/users/wpcarro/.gitsecret/keys/pubring.kbx~ new file mode 100644 index 000000000000..c0a748ce2c37 --- /dev/null +++ b/users/wpcarro/.gitsecret/keys/pubring.kbx~ Binary files differdiff --git a/users/wpcarro/.gitsecret/keys/trustdb.gpg b/users/wpcarro/.gitsecret/keys/trustdb.gpg new file mode 100644 index 000000000000..369485be0624 --- /dev/null +++ b/users/wpcarro/.gitsecret/keys/trustdb.gpg Binary files differdiff --git a/users/wpcarro/.gitsecret/paths/mapping.cfg b/users/wpcarro/.gitsecret/paths/mapping.cfg new file mode 100644 index 000000000000..fda2c84fb3d8 --- /dev/null +++ b/users/wpcarro/.gitsecret/paths/mapping.cfg @@ -0,0 +1 @@ +secrets.json:7d596a3ed16403040d89dd7e033a2af58e7aaabb6f246f44751b80a1863a2949 diff --git a/users/wpcarro/.skip-subtree b/users/wpcarro/.skip-subtree new file mode 100644 index 000000000000..968b9b74d7be --- /dev/null +++ b/users/wpcarro/.skip-subtree @@ -0,0 +1,2 @@ +Do not recurse from top-level readTree, while this is being refactored +we have a nested tree. diff --git a/users/wpcarro/Makefile b/users/wpcarro/Makefile new file mode 100644 index 000000000000..6ef5116e3ef5 --- /dev/null +++ b/users/wpcarro/Makefile @@ -0,0 +1,9 @@ +install: + source "${BRIEFCASE}/configs/install" + +uninstall: + source "${BRIEFCASE}/configs/uninstall" + +list-broken-links: + find "${HOME}" -maxdepth 1 -xtype l && \ + find "${HOME}/.config" -maxdepth 1 -xtype l diff --git a/users/wpcarro/README.md b/users/wpcarro/README.md new file mode 100644 index 000000000000..adfe30ff30db --- /dev/null +++ b/users/wpcarro/README.md @@ -0,0 +1,70 @@ +# briefcase + +[![Build status](https://badge.buildkite.com/aa0d413bfeedcafd8719f977eadd40e04d0b5334fc7f58e8ee.svg)](https://buildkite.com/wpcarros-infrastructure/post-receive) + +Welcome to my monorepo: briefcase. + +Herein you will find a variety of libraries, packages, and documents. Some of +this work in finished and other work is incomplete or just a sketch for a +future project. + +Where applicable, I try to include `README.md` files in some of the +subdirectories to help orient both myself and any onlookers. + +## Languages + +To give you a general idea of the source code inside of this monorepo, here is +the latest output from `tokei --hidden --sort code .`: + +```text +------------------------------------------------------------------------------- + Language Files Lines Code Comments Blanks +------------------------------------------------------------------------------- + Emacs Lisp 81 22267 13847 5661 2759 + Python 177 10575 7930 885 1760 + Elm 34 5345 4277 219 849 + Haskell 50 4263 3111 428 724 + Nix 66 1581 1379 66 136 + TypeScript 19 1345 1067 90 188 + Go 17 1256 926 173 157 + Vim Script 2 766 470 87 209 + Elixir 13 358 301 8 49 + JavaScript 9 77 73 0 4 + Lisp 3 83 43 23 17 + Shell 3 55 30 11 14 + Clojure 2 10 8 0 2 + C 1 6 5 0 1 + Rust 1 5 3 1 1 +------------------------------------------------------------------------------- + Total 478 47992 33470 7652 6870 +------------------------------------------------------------------------------- +``` + +## Sign posts + +Below I have outlined a few projects that you might find interesting. I am +using `//` to indicate the root of my monorepo, the directory in which this +`README.md` resides. + +- `//boilerplate`: scaffolding for projects. Boilerplate's goal is to + reduce the startup costs of a project. +- `//configs`: my dotfiles (e.g. `config.fish`, `init.vim`). +- `//emacs`: Emacs is both my preferred text editor and my window manager; with + tens of thousands of lines of Emacs Lisp, you can safely assume that this + directory hosts a lot of libraries and packages. +- `//monzo_ynab`: `systemd` timer unit that imports my Monzo (i.e. a U.K.-based + online bank) transactions into the personal finance tool YNAB (i.e. + youneedabudget.com). +- `//nixos`: my declarative configuration for my NixOS machines. If you are + unfamiliar with Nix, I recommend reading about the NixOS project. +- `//tools`: some scripts and projects that simplify my life. +- `//website`: everything required to build my website, wpcarro.dev. + +## Notes to self + +Here are a few reminders when setting up a new machine: + +- Ensure `~/.password-store` exists. +- Run `export_gpg` from a computer with my gpg credentials. Run `import_gpg` + from the new machine. +- Ensure the new machine can access my Github. diff --git a/users/wpcarro/assessments/brilliant/.ghci b/users/wpcarro/assessments/brilliant/.ghci new file mode 100644 index 000000000000..efc88e630ccb --- /dev/null +++ b/users/wpcarro/assessments/brilliant/.ghci @@ -0,0 +1,2 @@ +:set prompt "> " +:set -Wall diff --git a/users/wpcarro/assessments/brilliant/App.hs b/users/wpcarro/assessments/brilliant/App.hs new file mode 100644 index 000000000000..0272988f371c --- /dev/null +++ b/users/wpcarro/assessments/brilliant/App.hs @@ -0,0 +1,41 @@ +-------------------------------------------------------------------------------- +module App where +-------------------------------------------------------------------------------- +import Keyboard (Keyboard(..)) +import Transforms (Transform(..)) +import Utils ((|>)) + +import qualified Data.Char as Char +import qualified Utils +import qualified Data.List.Split as Split +import qualified Keyboard +import qualified Data.HashMap.Strict as HM +-------------------------------------------------------------------------------- + +transform :: Keyboard -> Transform -> Keyboard + +transform (Keyboard xs) xform = + case xform of + HorizontalFlip -> + xs + |> fmap reverse + |> Keyboard + + VerticalFlip -> + xs + |> reverse + |> Keyboard + + Shift n -> + xs + |> concat + |> Utils.rotate n + |> Split.chunksOf 10 + |> Keyboard + +retypePassage :: String -> Keyboard -> Maybe String +retypePassage passage newKeyboard = + passage + |> fmap Char.toUpper + |> traverse (\c -> HM.lookup c Keyboard.charToCoord) + >>= traverse (Keyboard.coordToChar newKeyboard) diff --git a/users/wpcarro/assessments/brilliant/Keyboard.hs b/users/wpcarro/assessments/brilliant/Keyboard.hs new file mode 100644 index 000000000000..13b5de0145aa --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Keyboard.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +-------------------------------------------------------------------------------- +module Keyboard where +-------------------------------------------------------------------------------- +import Utils +import Data.Coerce +import Data.Hashable (Hashable) +import GHC.Generics (Generic) + +import qualified Data.List as List +import qualified Data.HashMap.Strict as HM +-------------------------------------------------------------------------------- + +newtype Keyboard = Keyboard [[Char]] + deriving (Eq) + +instance Show Keyboard where + show (Keyboard xxs) = + xxs |> fmap printRow |> List.intercalate "\n" + where + printRow :: [Char] -> String + printRow xs = + xs |> fmap (\x -> '[':x:']':"") |> List.intercalate "" + +data Coord = Coord + { row :: Int + , col :: Int + } deriving (Eq, Show, Generic) + +instance Hashable Coord + +-- | List of characters to their QWERTY coordinatees. +coords :: [(Char, Coord)] +coords = + qwerty + |> coerce + |> fmap (zip [0..]) + |> zip [0..] + |> fmap (\(row, xs) -> xs |> fmap (\(col, char) -> (char, Coord row col))) + |> mconcat + +-- | Mapping of characters to their coordinates on a QWERTY keyboard with the +-- top-left corner as 0,0. +charToCoord :: HM.HashMap Char Coord +charToCoord = HM.fromList coords + +coordToChar :: Keyboard -> Coord -> Maybe Char +coordToChar (Keyboard xxs) Coord{..} = + Just $ xxs !! row !! col + +qwerty :: Keyboard +qwerty = Keyboard [ ['1','2','3','4','5','6','7','8','9','0'] + , ['Q','W','E','R','T','Y','U','I','O','P'] + , ['A','S','D','F','G','H','J','K','L',';'] + , ['Z','X','C','V','B','N','M',',','.','/'] + ] diff --git a/users/wpcarro/assessments/brilliant/Main.hs b/users/wpcarro/assessments/brilliant/Main.hs new file mode 100644 index 000000000000..e94c73bea287 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Main.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import Options.Applicative +import Data.Semigroup ((<>)) + +import qualified Transforms +import qualified Keyboard +import qualified App +-------------------------------------------------------------------------------- + +data CommandArgs = CommandArgs + { transforms :: String + , passage :: String + } deriving (Eq, Show) + +parseArgs :: Parser CommandArgs +parseArgs = + CommandArgs <$> strOption + ( long "transforms" + <> short 't' + <> help "String of transforms where (e.g. \"HHVS12VHVHS3\")" ) + <*> strOption + ( long "passage" + <> short 'p' + <> help "Input text to re-type" ) + +main :: IO () +main = do + CommandArgs{..} <- execParser opts + case Transforms.fromString transforms of + Nothing -> putStrLn "You must provide valid input (e.g. \"HHVS12VHVHS3\")" + Just xs -> do + let keyboard = foldl App.transform Keyboard.qwerty (Transforms.optimize xs) + putStrLn $ "Typing: \"" ++ passage ++ "\"\nOn this keyboard:\n" ++ show keyboard + case App.retypePassage passage keyboard of + Nothing -> putStrLn $ "Looks like at least one of the characters in your input passage doesn't fit on our QWERTY keyboard: \n" ++ show Keyboard.qwerty + Just result -> putStrLn $ "Result: " ++ result + where + opts = info (parseArgs <**> helper) + ( fullDesc + <> progDesc "Transform a QWERTY keyboard using a string of commands") diff --git a/users/wpcarro/assessments/brilliant/README.md b/users/wpcarro/assessments/brilliant/README.md new file mode 100644 index 000000000000..60d7de4e25ae --- /dev/null +++ b/users/wpcarro/assessments/brilliant/README.md @@ -0,0 +1,82 @@ +# Transform QWERTY + +Apply a series of transforms to a QWERTY keyboard then use the new keyboard to +re-type a passage of text. + +## Environment + +You will need [Nix][nix] to build this program on your machine. The good news is +that you won't need any Haskell-specific dependencies like `ghc`, `cabal`, or +`stack`: just Nix. + +Once you have Nix installed, to build the program, run the following from this +project's top-level directory: + +```shell +$ nix-build +``` + +This should output an executable named `transform-keyboard` within a `result` +directory: + +```shell +$ tree result +result +โโโ transform-keyboard +``` + +### Testing + +To run the test suite, run the following from the project's top-level directory: + +```shell +$ nix-shell +$ runhaskell Spec.hs +``` + +[nix]: https://nixos.org/download.html + +## Usage + +Here are some `--help` and usage examples: + +```shell +$ ./result/transform-keyboard --help +Usage: transform-keyboard (-t|--transforms ARG) (-p|--passage ARG) + Transform a QWERTY keyboard using a string of commands + +Available options: + -t,--transforms ARG String of transforms where (e.g. "HHVS12VHVHS3") + -p,--passage ARG Input text to re-type + -h,--help Show this help text +``` + +Now a working example: + +```shell +$ ./result/transform-keyboard --transforms=HHVS12VHVHS3 --passage='Hello,Brilliant.' +Typing: "Hello,Brilliant." +On this keyboard: +[H][J][K][L][;][Q][W][E][R][T] +[Y][U][I][O][P][1][2][3][4][5] +[6][7][8][9][0][Z][X][C][V][B] +[N][M][,][.][/][A][S][D][F][G] +Result: ZIVV4D/O3VV36APF +``` + +...and an example with an erroneous input (i.e. `!`): + +```shell +$ ./result/transform-keyboard --transforms=HHVS12VHVHS3 --passage='Hello,Brilliant!' +Typing: "Hello,Brilliant!" +On this keyboard: +[H][J][K][L][;][Q][W][E][R][T] +[Y][U][I][O][P][1][2][3][4][5] +[6][7][8][9][0][Z][X][C][V][B] +[N][M][,][.][/][A][S][D][F][G] +Looks like at least one of the characters in your input passage doesn't fit on our QWERTY keyboard: +[1][2][3][4][5][6][7][8][9][0] +[Q][W][E][R][T][Y][U][I][O][P] +[A][S][D][F][G][H][J][K][L][;] +[Z][X][C][V][B][N][M][,][.][/] +``` diff --git a/users/wpcarro/assessments/brilliant/Spec.hs b/users/wpcarro/assessments/brilliant/Spec.hs new file mode 100644 index 000000000000..e99e025641fa --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Spec.hs @@ -0,0 +1,103 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import Test.Hspec +import Test.QuickCheck +import Keyboard (Keyboard(..)) +import Transforms (Transform(..)) +import Data.Coerce +import Utils + +import qualified App +import qualified Keyboard +import qualified Transforms +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "Keyboard.print" $ do + it "pretty-prints the keyboard" $ do + show Keyboard.qwerty == "[1][2][3][4][5][6][7][8][9][0]\n[Q][W][E][R][T][Y][U][I][O][P]\n[A][S][D][F][G][H][J][K][L][;]\n[Z][X][C][V][B][N][M][,][.][/]" + + describe "Transforms.fromString" $ do + it "successfully parses a string of commands" $ do + Transforms.fromString "HHVS-12VHVHS3" == + Just [ HorizontalFlip + , HorizontalFlip + , VerticalFlip + , Shift (-12) + , VerticalFlip + , HorizontalFlip + , VerticalFlip + , HorizontalFlip + , Shift 3 + ] + + it "returns Nothing when the input is invalid" $ do + Transforms.fromString "potato" == Nothing + + it "return Nothing when the input is valid except for the end" $ do + Transforms.fromString "HVS10potato" == Nothing + + describe "App.transform" $ do + it "flips any keyboard horizontally" $ do + property $ \first second third fourth -> + App.transform (Keyboard [first, second, third, fourth]) HorizontalFlip == do + Keyboard [ reverse first + , reverse second + , reverse third + , reverse fourth + ] + + it "flips any keyboard vertically" $ do + property $ \first second third fourth -> + App.transform (Keyboard [first, second, third, fourth]) VerticalFlip == do + Keyboard $ reverse [first, second, third, fourth] + + it "shifts any keyboard" $ do + property $ \first second third fourth n -> + App.transform (Keyboard [first, second, third, fourth]) (Shift n) + |> (coerce :: Keyboard -> [[Char]]) + |> concat == + [first, second, third, fourth] + |> concat + |> Utils.rotate n + + it "flips a QWERTY keyboard horizontally" $ do + App.transform Keyboard.qwerty HorizontalFlip == do + Keyboard [ ['0','9','8','7','6','5','4','3','2','1'] + , ['P','O','I','U','Y','T','R','E','W','Q'] + , [';','L','K','J','H','G','F','D','S','A'] + , ['/','.',',','M','N','B','V','C','X','Z'] + ] + + it "flips a keyboard vertically" $ do + App.transform Keyboard.qwerty VerticalFlip == do + Keyboard [ ['Z','X','C','V','B','N','M',',','.','/'] + , ['A','S','D','F','G','H','J','K','L',';'] + , ['Q','W','E','R','T','Y','U','I','O','P'] + , ['1','2','3','4','5','6','7','8','9','0'] + ] + + it "shifts a keyboard left N times" $ do + App.transform Keyboard.qwerty (Shift 2) == do + Keyboard [ ['3','4','5','6','7','8','9','0','Q','W'] + , ['E','R','T','Y','U','I','O','P','A','S'] + , ['D','F','G','H','J','K','L',';','Z','X'] + , ['C','V','B','N','M',',','.','/','1','2'] + ] + + it "shifts right negative amounts" $ do + App.transform Keyboard.qwerty (Shift (-3)) == do + Keyboard [ [',','.','/','1','2','3','4','5','6','7'] + , ['8','9','0','Q','W','E','R','T','Y','U'] + , ['I','O','P','A','S','D','F','G','H','J'] + , ['K','L',';','Z','X','C','V','B','N','M'] + ] + + describe "Transforms.optimize" $ do + it "removes superfluous horizontal transformations" $ do + Transforms.optimize [HorizontalFlip, HorizontalFlip] == [] + + it "removes superfluous vertical transformations" $ do + Transforms.optimize [VerticalFlip, VerticalFlip] == [] diff --git a/users/wpcarro/assessments/brilliant/Transforms.hs b/users/wpcarro/assessments/brilliant/Transforms.hs new file mode 100644 index 000000000000..d8df8f8372e0 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Transforms.hs @@ -0,0 +1,52 @@ +-------------------------------------------------------------------------------- +module Transforms where +-------------------------------------------------------------------------------- +import Control.Applicative ((<|>)) +import Text.ParserCombinators.ReadP +-------------------------------------------------------------------------------- + +data Transform = VerticalFlip + | HorizontalFlip + | Shift Int + deriving (Eq, Show) + +digit :: ReadP Char +digit = + satisfy (\c -> c >= '0' && c <= '9') + +command :: ReadP Transform +command = vertical + <|> horizontal + <|> shift + where + vertical = + char 'V' >> pure VerticalFlip + + horizontal = + char 'H' >> pure HorizontalFlip + + shift = do + _ <- char 'S' + negative <- option Nothing $ fmap Just (satisfy (== '-')) + n <- read <$> many1 digit + case negative of + Nothing -> pure $ Shift n + Just _ -> pure $ Shift (-1 * n) + +-- | Attempt to remove redundant transformations. +-- | Here are some rules that I'd like to support but may not have time for: +-- | - All even-numbered flips (w/o intermittent shifts) can become zero +-- | - All odd-numbered flips (w/o intermittent shifts) can become 1 +-- | - All shifts can be be reduce to the absolute value of shifts +optimize :: [Transform] -> [Transform] +optimize [] = [] +optimize [x] = [x] +optimize (VerticalFlip:VerticalFlip:xs) = optimize xs +optimize (HorizontalFlip:HorizontalFlip:xs) = optimize xs +optimize xs = xs + +fromString :: String -> Maybe [Transform] +fromString x = + case readP_to_S (manyTill command eof) x of + [(res, "")] -> Just res + _ -> Nothing diff --git a/users/wpcarro/assessments/brilliant/Utils.hs b/users/wpcarro/assessments/brilliant/Utils.hs new file mode 100644 index 000000000000..c69d00333b8e --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Utils.hs @@ -0,0 +1,13 @@ +-------------------------------------------------------------------------------- +module Utils where +-------------------------------------------------------------------------------- +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +(|>) :: a -> (a -> b) -> b +(|>) = (&) + +-- | Rotate `xs` as a cycle `n` times. +rotate :: Int -> [a] -> [a] +rotate n xs = take size . drop (n `mod` size) . cycle $ xs + where size = length xs diff --git a/users/wpcarro/assessments/brilliant/default.nix b/users/wpcarro/assessments/brilliant/default.nix new file mode 100644 index 000000000000..886ba87e1caf --- /dev/null +++ b/users/wpcarro/assessments/brilliant/default.nix @@ -0,0 +1,16 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.program { + name = "transform-keyboard"; + srcs = builtins.path { + path = ./.; + name = "transform-keyboard-src"; + }; + deps = hpkgs: with hpkgs; [ + optparse-applicative + unordered-containers + split + rio + ]; + ghcExtensions = []; +} diff --git a/users/wpcarro/assessments/brilliant/shell.nix b/users/wpcarro/assessments/brilliant/shell.nix new file mode 100644 index 000000000000..d0a6c7e5e6f5 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/shell.nix @@ -0,0 +1,16 @@ +let + pkgs = import (builtins.fetchGit { + url = "https://github.com/NixOS/nixpkgs-channels"; + ref = "nixos-20.03"; + rev = "afa9ca61924f05aacfe495a7ad0fd84709d236cc"; + }) {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + (haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [ + hspec + optparse-applicative + unordered-containers + split + ])) + ]; +} diff --git a/users/wpcarro/assessments/dotted-squares/.envrc b/users/wpcarro/assessments/dotted-squares/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/assessments/dotted-squares/.ghci b/users/wpcarro/assessments/dotted-squares/.ghci new file mode 100644 index 000000000000..b100af4432c5 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/.ghci @@ -0,0 +1 @@ +:set -Wall diff --git a/users/wpcarro/assessments/dotted-squares/Main.hs b/users/wpcarro/assessments/dotted-squares/Main.hs new file mode 100644 index 000000000000..44f91e2b2311 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/Main.hs @@ -0,0 +1,218 @@ +{-# LANGUAGE DeriveGeneric #-} +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import Data.Hashable +import Data.Function ((&)) +import GHC.Generics +import Text.ParserCombinators.ReadP +import Control.Applicative + +import qualified Data.HashSet as HS +-------------------------------------------------------------------------------- + +data Direction + = DirLeft + | DirRight + | DirUp + | DirDown + deriving (Eq, Show) + +data Point = Point Int Int + deriving (Eq, Show, Ord, Generic) +instance Hashable Point + +data Orientation + = Horizontal + | Vertical + deriving (Eq, Show) + +data Anchor + = Beg + | End + deriving (Eq, Show) + +data Rotation + = CW + | CCW + deriving (Eq, Show) + +data Line = Line Point Point + deriving (Show, Generic) +instance Hashable Line + +instance Eq Line where + Line begA endA == Line begB endB = + (begA == begB && endA == endB) || + (begA == endB && endA == begB) + +data Game = Game (HS.HashSet Line) [Line] + deriving (Eq, Show) + +data Scoreboard = Scoreboard Int Int + deriving (Eq) + +instance Semigroup Scoreboard where + (Scoreboard a b) <> (Scoreboard x y) = + Scoreboard (a + x) (b + y) + +instance Monoid Scoreboard where + mempty = Scoreboard 0 0 + +data Turn + = Player1 + | Player2 + deriving (Eq, Show) + +next :: Turn -> Turn +next Player1 = Player2 +next Player2 = Player1 + +instance Show Scoreboard where + show (Scoreboard p1 p2) = + "Player 1: " ++ show (p1) ++ " Player 2: " ++ show (p2) + +digit :: ReadP Char +digit = satisfy (\c -> c >= '0' && c <= '9') + +int :: ReadP Int +int = read <$> many1 digit + +inputLine :: ReadP String +inputLine = manyTill get (char '\n') + +direction :: ReadP Direction +direction = do + c <- char 'L' <|> char 'R' <|> char 'U' <|> char 'D' + case c of + 'L' -> pure DirLeft + 'R' -> pure DirRight + 'U' -> pure DirUp + 'D' -> pure DirDown + _ -> fail $ "Unexpected direction: " ++ show c + +validMove :: Int -> Int -> ReadP Line +validMove w h = do + x <- int + skipSpaces + y <- int + skipSpaces + dir <- direction + _ <- char '\n' + if x >= 0 && x <= w && y >= 0 && y <= h then do + let beg = Point x y + pure $ mkLine beg (shiftPoint dir beg) + else + fail "Expected a move on the game board" + +game :: ReadP Game +game = do + w <- read <$> inputLine + h <- read <$> inputLine + locs <- read <$> inputLine + moves <- count locs (validMove w h) + eof + pure $ Game mempty moves + +parseInput :: String -> Maybe Game +parseInput x = do + case readP_to_S game x of + [(res, "")] -> Just res + _ -> Nothing + +-- | Smart constructor to ensure that beg is always < end. +mkLine :: Point -> Point -> Line +mkLine beg end = + if beg < end then Line beg end else Line end beg + +mkLineDir :: Int -> Int -> Direction -> Line +mkLineDir x y dir = + let beg = Point x y + in mkLine beg (shiftPoint dir beg) + +mkLineDir' :: Point -> Direction -> Line +mkLineDir' (Point x y) dir = mkLineDir x y dir + +shiftPoint :: Direction -> Point -> Point +shiftPoint DirLeft (Point x y) = Point (x - 1) y +shiftPoint DirRight (Point x y) = Point (x + 1) y +shiftPoint DirUp (Point x y) = Point x (y + 1) +shiftPoint DirDown (Point x y) = Point x (y - 1) + +shiftLine :: Direction -> Line -> Line +shiftLine dir (Line beg end) = + mkLine (shiftPoint dir beg) (shiftPoint dir end) + +rotateLine :: Anchor -> Rotation -> Line -> Line +rotateLine anchor rotation line = + doRotateLine (classifyOrientation line) anchor rotation line + +doRotateLine :: Orientation -> Anchor -> Rotation -> Line -> Line +doRotateLine Horizontal Beg CW (Line beg _) = mkLineDir' beg DirDown +doRotateLine Horizontal Beg CCW (Line beg _) = mkLineDir' beg DirUp +doRotateLine Horizontal End CW (Line _ end) = mkLineDir' end DirUp +doRotateLine Horizontal End CCW (Line _ end) = mkLineDir' end DirDown +doRotateLine Vertical Beg CW (Line beg _) = mkLineDir' beg DirRight +doRotateLine Vertical Beg CCW (Line beg _) = mkLineDir' beg DirLeft +doRotateLine Vertical End CW (Line _ end) = mkLineDir' end DirLeft +doRotateLine Vertical End CCW (Line _ end) = mkLineDir' end DirRight + +classifyOrientation :: Line -> Orientation +classifyOrientation (Line (Point _ y1) (Point _ y2)) = + if y1 == y2 then Horizontal else Vertical + +closesAnySquare :: HS.HashSet Line -> Line -> Bool +closesAnySquare allMoves line = do + let alreadyDrawn x = HS.member x allMoves + case classifyOrientation line of + Horizontal -> + all alreadyDrawn + [ shiftLine DirUp line + , rotateLine Beg CCW line + , rotateLine End CW line + ] || + all alreadyDrawn + [ shiftLine DirDown line + , rotateLine Beg CW line + , rotateLine End CCW line + ] + Vertical -> + all alreadyDrawn + [ shiftLine DirLeft line + , rotateLine Beg CCW line + , rotateLine End CW line + ] || + all alreadyDrawn + [ shiftLine DirRight line + , rotateLine Beg CW line + , rotateLine End CCW line + ] + +incScoreboard :: Turn -> Scoreboard -> Scoreboard +incScoreboard Player1 score = score <> Scoreboard 1 0 +incScoreboard Player2 score = score <> Scoreboard 0 1 + +scoreGame :: Turn -> Game -> Scoreboard -> Maybe Scoreboard +scoreGame _ (Game _ []) score = Just $ score +scoreGame player (Game allMoves (line:rest)) score = + if HS.member line allMoves then + Nothing + else do + let allMoves' = HS.insert line allMoves + score' = if closesAnySquare allMoves line then + incScoreboard player score + else score + scoreGame (next player) (Game allMoves' rest) score' + +(|>) :: a -> (a -> b) -> b +(|>) = (&) + +main :: IO () +main = do + input <- readFile "game.txt" + case parseInput input of + Nothing -> putStrLn "invalid" + Just parsedGame -> + case scoreGame Player1 parsedGame mempty of + Nothing -> putStrLn "invalid" + Just score -> print score diff --git a/users/wpcarro/assessments/dotted-squares/README.md b/users/wpcarro/assessments/dotted-squares/README.md new file mode 100644 index 000000000000..3d13da1cb18e --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/README.md @@ -0,0 +1,21 @@ +# Dotted Squares + +This is my second attempt at solving this problem. I had an hour to solve it the +first time, and I unfortunately came up short although I made good progress. + +The problem asks to read input from a text file that looks like this: + +``` +1 -- board width +1 -- board height +4 -- number of lines of "moves" (below) +0 0 R -- create a unit vector (0,0) facing right +0 0 U -- create a unit vector (0,0) facing up +0 1 L -- create a unit vector (0,1) facing left +1 1 D -- create a unit vector (1,1) facing down +``` + +After parsing and validating the input, score the outcome a game where players +one and two alternatively take turns drawing lines on a board. Anytime one of +the players draws a line that creates a square from existing lines, they get a +point. diff --git a/users/wpcarro/assessments/dotted-squares/Spec.hs b/users/wpcarro/assessments/dotted-squares/Spec.hs new file mode 100644 index 000000000000..b5d604085b9b --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/Spec.hs @@ -0,0 +1,80 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import Test.Hspec +import Main hiding (main) +import qualified Data.HashSet as HS +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "dotted-squares" $ do + describe "parseInput" $ do + it "works as expected" $ do + input <- readFile "input-a.txt" + parseInput input `shouldBe` Just (Game mempty [ mkLine (Point 0 0) (Point 1 0) + , mkLine (Point 0 0) (Point 0 1) + ]) + + it "fails when the game has too many user moves" $ do + input <- readFile "too-many-moves.txt" + parseInput input `shouldBe` Nothing + + it "fails when the game has too few user moves" $ do + input <- readFile "too-few-moves.txt" + parseInput input `shouldBe` Nothing + + describe "shiftLine" $ do + let horizontal = mkLineDir 1 1 DirRight + vertical = mkLineDir 1 1 DirUp + it "can move a horizontal line up" $ + shiftLine DirUp horizontal `shouldBe` mkLineDir 1 2 DirRight + it "can move a horizontal line down" $ + shiftLine DirDown horizontal `shouldBe` mkLineDir 1 0 DirRight + it "can move a horizontal line left" $ + shiftLine DirLeft horizontal `shouldBe` mkLineDir 0 1 DirRight + it "can move a horizontal line right" $ + shiftLine DirRight horizontal `shouldBe` mkLineDir 2 1 DirRight + it "can move a vertical line up" $ + shiftLine DirUp vertical `shouldBe` mkLineDir 1 2 DirUp + it "can move a vertical line down" $ + shiftLine DirDown vertical `shouldBe` mkLineDir 1 0 DirUp + it "can move a vertical line left" $ + shiftLine DirLeft vertical `shouldBe` mkLineDir 0 1 DirUp + it "can move a vertical line right" $ + shiftLine DirRight vertical `shouldBe` mkLineDir 2 1 DirUp + + describe "rotateLine" $ do + let horizontal = mkLineDir 1 1 DirRight -- 1,1;2,1 + vertical = mkLineDir 1 1 DirUp -- 1,1;1,2 + it "can rotate a horizontal line CW anchored at its beginning" $ + rotateLine Beg CW horizontal `shouldBe` mkLineDir 1 1 DirDown + it "can rotate a horizontal line CCW anchored at its beginning" $ + rotateLine Beg CCW horizontal `shouldBe` mkLineDir 1 1 DirUp + it "can rotate a horizontal line CW anchored at its end" $ + rotateLine End CW horizontal `shouldBe` mkLineDir 2 1 DirUp + it "can rotate a horizontal line CCW anchored at its end" $ + rotateLine End CCW horizontal `shouldBe` mkLineDir 2 1 DirDown + + it "can rotate a vertical line CW anchored at its beginning" $ + rotateLine Beg CW vertical `shouldBe` mkLineDir 1 1 DirRight + it "can rotate a vertical line CCW anchored at its beginning" $ + rotateLine Beg CCW vertical `shouldBe` mkLineDir 1 1 DirLeft + it "can rotate a vertical line CW anchored at its end" $ + rotateLine End CW vertical `shouldBe` mkLineDir 1 2 DirLeft + it "can rotate a vertical line CCW anchored at its end" $ + rotateLine End CCW vertical `shouldBe` mkLineDir 1 2 DirRight + + describe "closesAnySquare" $ do + let threeSides = [ (0, 0, DirRight) + , (0, 0, DirUp) + , (0, 1, DirRight) + ] + |> fmap (\(x, y, dir) -> mkLineDir x y dir) + |> HS.fromList + it "returns true the line we supply makes a square" $ + closesAnySquare threeSides (mkLineDir 1 1 DirDown) `shouldBe` True + it "returns false the line we supply doesn't make a square" $ + closesAnySquare threeSides (mkLineDir 1 1 DirUp) `shouldBe` False + it "returns false when we have no existing lines" $ + closesAnySquare mempty (mkLineDir 1 1 DirUp) `shouldBe` False diff --git a/users/wpcarro/assessments/dotted-squares/colliding-moves.txt b/users/wpcarro/assessments/dotted-squares/colliding-moves.txt new file mode 100644 index 000000000000..a831fa95c08e --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/colliding-moves.txt @@ -0,0 +1,7 @@ +1 +1 +4 +0 0 R +0 0 R +0 1 R +0 1 R diff --git a/users/wpcarro/assessments/dotted-squares/game.txt b/users/wpcarro/assessments/dotted-squares/game.txt new file mode 100644 index 000000000000..0af71d1f5b56 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/game.txt @@ -0,0 +1,7 @@ +1 +1 +4 +0 0 R +0 0 U +0 1 R +1 1 D diff --git a/users/wpcarro/assessments/dotted-squares/input-a.txt b/users/wpcarro/assessments/dotted-squares/input-a.txt new file mode 100644 index 000000000000..b9e871eced86 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/input-a.txt @@ -0,0 +1,5 @@ +1 +1 +2 +0 0 R +0 0 U diff --git a/users/wpcarro/assessments/dotted-squares/shell.nix b/users/wpcarro/assessments/dotted-squares/shell.nix new file mode 100644 index 000000000000..87eb23d731e2 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + hspec + unordered-containers + ]; +} diff --git a/users/wpcarro/assessments/dotted-squares/too-few-moves.txt b/users/wpcarro/assessments/dotted-squares/too-few-moves.txt new file mode 100644 index 000000000000..d684679d264f --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/too-few-moves.txt @@ -0,0 +1,6 @@ +1 +1 +4 +0 0 R +0 0 U +0 1 R diff --git a/users/wpcarro/assessments/dotted-squares/too-many-moves.txt b/users/wpcarro/assessments/dotted-squares/too-many-moves.txt new file mode 100644 index 000000000000..bfcced43b930 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/too-many-moves.txt @@ -0,0 +1,7 @@ +1 +1 +3 +0 0 R +0 0 U +0 1 R +1 1 D diff --git a/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py b/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py new file mode 100644 index 000000000000..d0d948402001 --- /dev/null +++ b/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py @@ -0,0 +1,29 @@ +# The file '2010.census.txt' contains summary statistics from the 2010 United +# States census including household income. The data is in an unspecified +# format. + +# Find the average of the column called: + +# 'MEDIAN HOUSEHOLD INCOME' + +# Ideally the solution should be a command line script, of the form: + +# $ ./solution [options] [file...] + +# The solution may be written in any language, Python is preferred but not +# required. + +# Google, stack overflow, etc. usage is allowed. + +import requests + +url = "https://assets.tryramp.com/interview/census/2010.census.txt" + +def main(): + res = requests.get(url) + if res.status not in {200}: + raise Exception("Unexpected status code: {}".format(res.status_code)) + # download the content + # parse row + # select 'MEDIAN HOUSEHOLD INCOME' column + pass diff --git a/users/wpcarro/assessments/ramp/solution.py b/users/wpcarro/assessments/ramp/solution.py new file mode 100644 index 000000000000..28060bfb3c40 --- /dev/null +++ b/users/wpcarro/assessments/ramp/solution.py @@ -0,0 +1,87 @@ +# The file '2010.census.txt' contains summary statistics from the 2010 United +# States census including household income. The data is in an unspecified +# format. + +# Find the average of the column called: + +# 'MEDIAN HOUSEHOLD INCOME' + +# Ideally the solution should be a command line script, of the form: + +# $ ./solution [options] [file...] + +# The solution may be written in any language, Python is preferred but not +# required. + +# Google, stack overflow, etc. usage is allowed. + +import requests +import csv + +url = "https://assets.tryramp.com/interview/census/2010.census.txt" +column = 'MEDIAN HOUSEHOLD INCOME' +columns = [ + 'CENSUS YEAR', + 'TRACT', + 'BLOCK GROUP', + 'FIPS ID', + 'TOTAL POPULATION', + 'POPULATION WHITE', + 'POPULATION BLACK', + 'POPULATION ASIAN', + 'POPULATION OTHER', + 'POPULATION AMERICAN INDIAN', + 'POPULATION PACIFIC ISLANDER', + 'POPULATION ONE RACE', + 'POPULATION MULTI RACE', + 'POPULATION 25 OLDER', + 'MEDIAN AGE', + 'MEDIAN HOUSEHOLD INCOME', + 'HIGH SCHOOL MALE', + 'HIGH SCHOOL MORE MALE', + 'COLLEGE 1 YR LESS MALE', + 'COLLEGE 1 YR MORE MALE', + 'ASSOCIATES DEGREE MALE', + 'BACHELORS DEGREE MALE', + 'MASTERS DEGREE MALE', + 'PROFESSIONAL DEGREE MALE', + 'DOCTORAL DEGREE MALE', + 'HIGH SCHOOL FEMALE', + 'HIGH SCHOOL MORE FEMALE', + 'COLLEGE 1 YR LESS FEMALE', + 'COLLEGE 1 YR MORE FEMALE', + 'ASSOCIATES DEGREE FEMALE', + 'BACHELORS DEGREE FEMALE', + 'MASTERS DEGREE FEMALE', + 'PROFESSIONAL DEGREE FEMALE', + 'DOCTORAL DEGREE FEMALE', + 'PERCENT 25 YR OVER HIGH SCHOOL MORE', + 'HOUSING UNITS', + 'OCCUPIED HOUSING UNITS', + 'OWNER OCCUPIED HOUSING', + 'RENTER OCCUPIED HOUSING', + 'PERCENT OWNER OCCUPIED', + 'PERCENT RENTER OCCUPIED', + 'MEDIAN HOUSE VALUE OWNER OCCUPIED', + 'MEDIAN YEAR BUILT', + 'VACANCY RATES', +] + + +def average(xs): + return sum(xs) / len(xs) + + +def parse_body(body): + return list(csv.DictReader(body.split('\n')[1:], delimiter='|', fieldnames=columns)) + + +def main(): + res = requests.get(url) + if res.status_code not in {200}: + raise Exception("Unexpected status code: {}".format(res.status_code)) + return average([int(d.get(column)) + for d in parse_body(res.text) + if int(d.get(column)) >= 0]) + +print(main()) diff --git a/users/wpcarro/assessments/semiprimes/.gitignore b/users/wpcarro/assessments/semiprimes/.gitignore new file mode 100644 index 000000000000..b5b25bd64822 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/.gitignore @@ -0,0 +1 @@ +default.nix diff --git a/users/wpcarro/assessments/semiprimes/README.md b/users/wpcarro/assessments/semiprimes/README.md new file mode 100644 index 000000000000..7d5a15482ab3 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/README.md @@ -0,0 +1,44 @@ +# Semiprimes Service + +## Introduction + +A **composite** is a number containing at least two prime factors. For example: + +``` +15 = 3 ร 5 +9 = 3 ร 3 +12 = 2 ร 2 ร 3 +``` + +There are ten composites below thirty containing precisely two, not necessarily +distinct, prime factors: `4, 6, 9, 10, 14, 15, 21, 22, 25, 26`. Letโs call such +numbers *Semiprimes*. + +## Task + +- Write a module which provides a function to tell whether a given number, `N`, + is a semiprime. `N` will be less than 100,000 +- Please implement an API (RESTful or GraphQL) to factor a given number into two + prime numbers if itโs a semiprime, otherwise, return an error message. + +## Stretch Goals + +- Handle the invalid inputs. +- Support batch requests: i.e. users could provide 100 numbers, and the API + return the answer for all. +- Considering this module will be used by a long running service, could you + optimize it to give answers faster? + +## Usage + +To run the application you'll need to have `elixir` installed. Assuming `elixir` +is already installed, consult the following steps to start the application: + +```shell +$ cd server +$ mix deps.get +$ iex -S mix +``` + +Now open a web browser and visit `http://localhost:8080`! + diff --git a/users/wpcarro/assessments/semiprimes/default.nix b/users/wpcarro/assessments/semiprimes/default.nix new file mode 100644 index 000000000000..29452eac250c --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/default.nix @@ -0,0 +1 @@ +# stubbed diff --git a/users/wpcarro/assessments/semiprimes/server/.formatter.exs b/users/wpcarro/assessments/semiprimes/server/.formatter.exs new file mode 100644 index 000000000000..d2cda26eddc9 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/users/wpcarro/assessments/semiprimes/server/.gitignore b/users/wpcarro/assessments/semiprimes/server/.gitignore new file mode 100644 index 000000000000..db9704a85ff6 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +server-*.tar + diff --git a/users/wpcarro/assessments/semiprimes/server/lib/app.ex b/users/wpcarro/assessments/semiprimes/server/lib/app.ex new file mode 100644 index 000000000000..7a6fa5ea248d --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/app.ex @@ -0,0 +1,8 @@ +defmodule App do + use Application + + @impl true + def start(_type, _args) do + Sup.start_link() + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/cache.ex b/users/wpcarro/assessments/semiprimes/server/lib/cache.ex new file mode 100644 index 000000000000..cd064cc1ae4b --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/cache.ex @@ -0,0 +1,41 @@ +defmodule Cache do + @moduledoc """ + Cache is an in-memory key-value store. + """ + use Agent + + @doc """ + Inititalize the key-value store. + """ + def start_link(_) do + Agent.start_link(fn -> %{} end, name: __MODULE__) + end + + @doc """ + Attempt to return the value stored at `key` + """ + def get(key) do + Agent.get(__MODULE__, &Map.get(&1, key)) + end + + @doc """ + Write the `value` under the `key`. Last writer wins. + """ + def put(key, value) do + Agent.update(__MODULE__, &Map.put(&1, key, value)) + end + + @doc """ + List the contents of the cache. Useful for debugging purposes. + """ + def list() do + Agent.get(__MODULE__, & &1) + end + + @doc """ + Invalidate the entire cache. + """ + def clear() do + Agent.update(__MODULE__, fn _ -> %{} end) + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/extras.ex b/users/wpcarro/assessments/semiprimes/server/lib/extras.ex new file mode 100644 index 000000000000..f0c2ea4b9e21 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/extras.ex @@ -0,0 +1,22 @@ +defmodule Extras do + @moduledoc """ + Hosts utility functions intended to supplement the standard library. + """ + + @doc """ + Return an ascending range starting at `a` and ending at `b` (exclusive). + + ## Examples + + iex> Extras.range(2, 5) + [2, 3, 4] + + """ + def range(a, b) do + if b <= a do + [] + else + [a] ++ range(a + 1, b) + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/math.ex b/users/wpcarro/assessments/semiprimes/server/lib/math.ex new file mode 100644 index 000000000000..8a33be475389 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/math.ex @@ -0,0 +1,26 @@ +defmodule Math do + @moduledoc """ + Math utilities. + """ + alias Extras + + @doc """ + Returns the prime factors for `n`. + + ## Examples + + iex> Math.factor(15) + [3, 5] + + """ + def factor(1), do: [] + + def factor(n) do + Extras.range(2, n - 1) + |> Enum.find(&(rem(n, &1) == 0)) + |> case do + nil -> [n] + x -> [x | factor(div(n, x))] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/router.ex b/users/wpcarro/assessments/semiprimes/server/lib/router.ex new file mode 100644 index 000000000000..cb55520920de --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/router.ex @@ -0,0 +1,86 @@ +defmodule Router do + use Plug.Router + use Plug.Debugger + require Logger + + plug(Plug.Logger, log: :debug) + plug(Plug.Parsers, parsers: [:urlencoded]) + plug(:match) + plug(:dispatch) + + @usage """ + Usage: Try querying some of the following endpoints... + GET / + GET /help + GET /semiprime?number=<integer> + GET /semiprimes?numbers=<comma-separated-integers> + """ + + get "/" do + send_resp(conn, 200, "Welcome to Semiprimes Service!\n\n#{@usage}") + end + + get "/help" do + send_resp(conn, 200, @usage) + end + + get "/semiprime" do + case conn |> Map.get(:query_params) |> Map.get("number") do + nil -> + send_resp(conn, 400, "You must pass an integer as a query parameter. #{@usage}") + + val -> + case Integer.parse(val) do + {n, ""} -> + send_resp(conn, 200, semiprime_response(n)) + + _ -> + send_resp(conn, 400, "We could not parse the number you provided.\n\n#{@usage}") + end + end + end + + get "/semiprimes" do + case conn |> Map.get(:query_params) |> Map.get("numbers") do + nil -> + send_resp( + conn, + 400, + "You must pass a comma-separated list of integers as a query parameter.\n\n#{@usage}" + ) + + xs -> + response = + xs + |> String.split(",") + |> Stream.map(&Integer.parse/1) + |> Stream.filter(fn + {n, ""} -> true + _ -> false + end) + |> Stream.map(fn {n, ""} -> semiprime_response(n) end) + |> Enum.join("\n") + + send_resp(conn, 200, response) + end + end + + match _ do + send_resp(conn, 404, "Not found.") + end + + ################################################################################ + # Utils + ################################################################################ + + defp semiprime_response(n) do + case Server.semiprime(n) do + nil -> + "#{n} is not a semiprime. Try another number!" + + {hit_or_miss, factors} -> + response = "#{n} is a semiprime! Its factors are #{Enum.join(factors, " and ")}." + "Cache #{Atom.to_string(hit_or_miss)} - #{response}" + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/server.ex b/users/wpcarro/assessments/semiprimes/server/lib/server.ex new file mode 100644 index 000000000000..7ab5e905b5a0 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/server.ex @@ -0,0 +1,33 @@ +defmodule Server do + @moduledoc """ + Documentation for `Server`. + """ + + @doc """ + If `n` contains exactly two prime factors, return those prime factors; + otherwise, return nothing. + """ + def semiprime(n) do + case Cache.get(n) do + nil -> + case do_semiprime(n) do + nil -> + nil + + res -> + Cache.put(n, res) + {:miss, res} + end + + hit -> + {:hit, hit} + end + end + + defp do_semiprime(n) do + case Math.factor(n) do + [_, _] = res -> res + _ -> nil + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/sup.ex b/users/wpcarro/assessments/semiprimes/server/lib/sup.ex new file mode 100644 index 000000000000..13a6ab374ff6 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/sup.ex @@ -0,0 +1,23 @@ +defmodule Sup do + @moduledoc """ + Top-level supervisor for our OTP application. For now, this supervisor starts + and monitors our cache. + """ + + use Supervisor + alias Plug.Adapters.Cowboy + + def start_link(opts \\ []) do + Supervisor.start_link(__MODULE__, :ok, opts) + end + + @impl true + def init(:ok) do + children = [ + Cache, + Cowboy.child_spec(scheme: :http, plug: Router, options: [port: 8000]) + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/mix.exs b/users/wpcarro/assessments/semiprimes/server/mix.exs new file mode 100644 index 000000000000..9062f927e7a8 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/mix.exs @@ -0,0 +1,32 @@ +defmodule Server.MixProject do + use Mix.Project + + def project do + [ + app: :server, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {App, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:cortex, "~> 0.1", only: [:dev, :test]}, + {:plug_cowboy, "~> 2.4.1"}, + {:cowboy, "~> 2.8.0"}, + {:plug, "~> 1.11.0"}, + {:poison, "~> 4.0.1"} + ] + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/mix.lock b/users/wpcarro/assessments/semiprimes/server/mix.lock new file mode 100644 index 000000000000..2ae7efbb3fb6 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/mix.lock @@ -0,0 +1,14 @@ +%{ + "cortex": {:hex, :cortex, "0.6.0", "8094830fae266eb0ae34d1a58983c0c49484341f5044fb4dfb81746647bd2993", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "d0ef5a2b1269626149118684dc4ea77dbfbc67017f4b4065b71dcefa26cfcc49"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, + "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, +} diff --git a/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs b/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs new file mode 100644 index 000000000000..67d0b8875cae --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs @@ -0,0 +1,18 @@ +defmodule ExtrasTest do + use ExUnit.Case + doctest Extras + + describe "range" do + test "returns an empty list for descending sequences" do + assert Extras.range(0, -2) == [] + end + + test "returns an empty list for non-ascending sequences" do + assert Extras.range(8, 8) == [] + end + + test "returns an exclusive range" do + assert Extras.range(3, 6) == [3, 4, 5] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/math_test.exs b/users/wpcarro/assessments/semiprimes/server/test/math_test.exs new file mode 100644 index 000000000000..c7186c824a8c --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/math_test.exs @@ -0,0 +1,30 @@ +defmodule MathTest do + use ExUnit.Case + doctest Math + + describe "factor" do + test "returns the prime factors for an input" do + [ + {15, [3, 5]}, + {12, [2, 2, 3]}, + {9, [3, 3]}, + {21, [3, 7]} + ] + |> Enum.map(fn {input, expected} -> + assert Math.factor(input) == expected + end) + end + + test "handles large numbers" do + assert Math.factor(104_023) == [17, 29, 211] + end + + test "returns an empty list for 1" do + assert Math.factor(1) == [] + end + + test "returns the prime number itself when the input is prime" do + assert Math.factor(7) == [7] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/server_test.exs b/users/wpcarro/assessments/semiprimes/server/test/server_test.exs new file mode 100644 index 000000000000..08d559734b5a --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/server_test.exs @@ -0,0 +1,34 @@ +defmodule ServerTest do + use ExUnit.Case + doctest Server + + describe "semiprime" do + test "returns the factors when the number is semiprime" do + Cache.clear() + # Semiprimes below 30 + [ + {4, [2, 2]}, + {6, [2, 3]}, + {9, [3, 3]}, + {10, [2, 5]}, + {14, [2, 7]}, + {15, [3, 5]}, + {21, [3, 7]}, + {22, [2, 11]}, + {25, [5, 5]}, + {26, [2, 13]} + ] + |> Enum.each(fn {input, expected} -> + assert Server.semiprime(input) == {:miss, expected} + end) + end + + test "returns nothing when the number is a composite number" do + # Composite numbers below 30 + [1, 2, 3, 5, 7, 8, 11, 12, 13, 16, 17, 18, 19, 20, 23, 24, 27, 28, 29] + |> Enum.each(fn x -> + assert Server.semiprime(x) == nil + end) + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs b/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs new file mode 100644 index 000000000000..869559e709ea --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/users/wpcarro/assessments/tt/.gitignore b/users/wpcarro/assessments/tt/.gitignore new file mode 100644 index 000000000000..d4d62d436b26 --- /dev/null +++ b/users/wpcarro/assessments/tt/.gitignore @@ -0,0 +1,6 @@ +.envrc +*.db +*.sqlite3 +!populate.sqlite3 +*.db-shm +*.db-wal \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/README.md b/users/wpcarro/assessments/tt/README.md new file mode 100644 index 000000000000..0231ef3ab8a4 --- /dev/null +++ b/users/wpcarro/assessments/tt/README.md @@ -0,0 +1,50 @@ +# TT + +All of the commands defined herein should be run from the top-level directory of +this repository (i.e. the directory in which this file exists). + +## Server + +To create the environment that contains all of this application's dependencies, +run: + +```shell +$ nix-shell +``` + +To run the server interactively, run: + +```shell +$ cd src/ +$ ghci +``` + +Now compile and load the server with: + +``` +Prelude> :l Main.hs +*Main> main +``` + +## Database + +Create a new database named `db.sqlite3` with: + +```shell +$ sqlite3 db.sqlite3 +``` + +Populate the database with: + +``` +sqlite3> .read populate.sqlite3 +``` + +You can verify that everything is setup with: + +``` +sqlite3> .tables +sqlite3> .schema +sqlite3> SELECT * FROM Accounts; +sqlite3> SELECT * FROM Trips; +``` diff --git a/users/wpcarro/assessments/tt/client/.gitignore b/users/wpcarro/assessments/tt/client/.gitignore new file mode 100644 index 000000000000..1cb4f3034cc3 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css diff --git a/users/wpcarro/assessments/tt/client/README.md b/users/wpcarro/assessments/tt/client/README.md new file mode 100644 index 000000000000..04804ad94fac --- /dev/null +++ b/users/wpcarro/assessments/tt/client/README.md @@ -0,0 +1,18 @@ +# Elm + +Elm has one of the best developer experiences that I'm aware of. The error +messages are helpful and the entire experience is optimized to improve the ease +of writing web applications. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ npx tailwindcss build index.css -o output.css +$ elm-live -- src/Main.elm --output=Main.min.js +``` + +You can now view your web client at `http://localhost:8000`! diff --git a/users/wpcarro/assessments/tt/client/dir-locals.nix b/users/wpcarro/assessments/tt/client/dir-locals.nix new file mode 100644 index 000000000000..5c3ae08870b0 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/dir-locals.nix @@ -0,0 +1,3 @@ +let + briefcase = import /home/wpcarro/briefcase {}; +in briefcase.utils.nixBufferFromShell ./shell.nix diff --git a/users/wpcarro/assessments/tt/client/elm.json b/users/wpcarro/assessments/tt/client/elm.json new file mode 100644 index 000000000000..c4095e118e24 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/elm.json @@ -0,0 +1,40 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "CurrySoftware/elm-datepicker": "4.0.0", + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/json": "1.1.3", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm-community/json-extra": "4.2.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0", + "justinmimbs/date": "3.2.1", + "krisajenkins/remotedata": "6.0.1", + "ryannhg/date-format": "2.3.0" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/file": "1.0.5", + "elm/parser": "1.1.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.3" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/assessments/tt/client/index.css b/users/wpcarro/assessments/tt/client/index.css new file mode 100644 index 000000000000..52114e0e9fb0 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/index.css @@ -0,0 +1,142 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.elm-datepicker--container { + position: relative; +} + +.elm-datepicker--input:focus { + outline: 0; +} + +.elm-datepicker--picker { + position: absolute; + border: 1px solid #CCC; + z-index: 10; + background-color: white; +} + +.elm-datepicker--picker-header, +.elm-datepicker--weekdays { + background: #F2F2F2; +} + +.elm-datepicker--picker-header { + display: flex; + align-items: center; +} + +.elm-datepicker--prev-container, +.elm-datepicker--next-container { + flex: 0 1 auto; + cursor: pointer; +} + +.elm-datepicker--month-container { + flex: 1 1 auto; + padding: 0.5em; + display: flex; + flex-direction: column; +} + +.elm-datepicker--month, +.elm-datepicker--year { + flex: 1 1 auto; + cursor: default; + text-align: center; +} + +.elm-datepicker--year { + font-size: 0.6em; + font-weight: 700; +} + +.elm-datepicker--prev, +.elm-datepicker--next { + border: 6px solid transparent; + background-color: inherit; + display: block; + width: 0; + height: 0; + padding: 0 0.2em; +} + +.elm-datepicker--prev { + border-right-color: #AAA; +} + +.elm-datepicker--prev:hover { + border-right-color: #BBB; +} + +.elm-datepicker--next { + border-left-color: #AAA; +} + +.elm-datepicker--next:hover { + border-left-color: #BBB; +} + +.elm-datepicker--table { + border-spacing: 0; + border-collapse: collapse; + font-size: 0.8em; +} + +.elm-datepicker--table td { + width: 2em; + height: 2em; + text-align: center; +} + +.elm-datepicker--row { + border-top: 1px solid #F2F2F2; +} + +.elm-datepicker--dow { + border-bottom: 1px solid #CCC; + cursor: default; +} + +.elm-datepicker--day { + cursor: pointer; +} + +.elm-datepicker--day:hover { + background: #F2F2F2; +} + +.elm-datepicker--disabled { + cursor: default; + color: #DDD; +} + +.elm-datepicker--disabled:hover { + background: inherit; +} + +.elm-datepicker--picked { + color: white; + background: darkblue; +} + +.elm-datepicker--picked:hover { + background: darkblue; +} + +.elm-datepicker--today { + font-weight: bold; +} + +.elm-datepicker--other-month { + color: #AAA; +} + +.elm-datepicker--other-month.elm-datepicker--disabled { + color: #EEE; +} + +.elm-datepicker--other-month.elm-datepicker--picked { + color: white; +} diff --git a/users/wpcarro/assessments/tt/client/index.html b/users/wpcarro/assessments/tt/client/index.html new file mode 100644 index 000000000000..9e6cef70dbb4 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/index.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="google-signin-client_id" content="580018768696-4beppspj6cu7rhjnfgok8lbmh9a4n3ok.apps.googleusercontent.com"> + <title>Elm SPA</title> + <link rel="stylesheet" type="text/css" href="./output.css" /> + <link rel="stylesheet" type="text/css" href="./print.css" media="print" /> + <script src="https://apis.google.com/js/platform.js" async defer></script> + <script src="./Main.min.js"></script> + </head> + <body class="font-serif"> + <div id="mount"></div> + <script> + function onSignIn(googleUser) { + console.log(googleUser); + } + + var app = Elm.Main.init({node: document.getElementById("mount")}); + + app.ports.printPage.subscribe(function() { + window.print(); + }); + + app.ports.googleSignIn.subscribe(function() { + var auth2 = gapi.auth2.getAuthInstance(); + var googleUser = auth2.signIn(); + }); + + app.ports.googleSignOut.subscribe(function() { + var auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(function() { + console.log('Google user successfully signed out.'); + }); + }); + </script> + </body> +</html> diff --git a/users/wpcarro/assessments/tt/client/print.css b/users/wpcarro/assessments/tt/client/print.css new file mode 100644 index 000000000000..3cfb279230cb --- /dev/null +++ b/users/wpcarro/assessments/tt/client/print.css @@ -0,0 +1,3 @@ +.no-print { + display: none; +} diff --git a/users/wpcarro/assessments/tt/client/shell.nix b/users/wpcarro/assessments/tt/client/shell.nix new file mode 100644 index 000000000000..15ac040b9462 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/shell.nix @@ -0,0 +1,10 @@ +let + pkgs = import <nixpkgs> {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + elmPackages.elm + elmPackages.elm-format + elmPackages.elm-live + ]; +} diff --git a/users/wpcarro/assessments/tt/client/src/Admin.elm b/users/wpcarro/assessments/tt/client/src/Admin.elm new file mode 100644 index 000000000000..d95609ee15e4 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Admin.elm @@ -0,0 +1,189 @@ +module Admin exposing (render) + +import Common +import Date +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Maybe.Extra as ME +import RemoteData +import State +import Tailwind +import UI +import Utils + + +roleToggle : State.Model -> State.Role -> Html State.Msg +roleToggle model role = + div [ [ "px-1", "inline" ] |> Tailwind.use |> class ] + [ UI.toggleButton + { toggled = model.inviteRole == Just role + , label = State.roleToString role + , handleEnable = State.UpdateInviteRole (Just role) + , handleDisable = State.UpdateInviteRole Nothing + } + ] + + +inviteUser : State.Model -> Html State.Msg +inviteUser model = + div [ [ "pb-6" ] |> Tailwind.use |> class ] + [ UI.header 3 "Invite a user" + , UI.textField + { handleInput = State.UpdateInviteEmail + , inputId = "invite-email" + , inputValue = model.inviteEmail + , pholder = "Email..." + } + , div [ [ "pt-4" ] |> Tailwind.use |> class ] + [ roleToggle model State.User + , roleToggle model State.Manager + , roleToggle model State.Admin + ] + , UI.baseButton + { enabled = + List.all + identity + [ String.length model.inviteEmail > 0 + , ME.isJust model.inviteRole + ] + , extraClasses = [ "my-4" ] + , label = + case model.inviteResponseStatus of + RemoteData.Loading -> + "Sending..." + + _ -> + "Send invitation" + , handleClick = + case model.inviteRole of + Nothing -> + State.DoNothing + + Just role -> + State.AttemptInviteUser role + } + ] + + +allTrips : State.Model -> Html State.Msg +allTrips model = + case model.trips of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetTrips } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\trip -> + li [] + [ UI.paragraph (Date.toIsoString trip.startDate ++ " - " ++ Date.toIsoString trip.endDate ++ ", " ++ trip.username ++ " is going " ++ trip.destination) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteTrip trip + } + ] + ) + ) + + +allUsers : State.Model -> Html State.Msg +allUsers model = + case model.accounts of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetAccounts } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\account -> + li [] + [ UI.paragraph + (account.username + ++ " - " + ++ State.roleToString account.role + ) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteAccount account.username + } + ] + ) + ) + + +users : List String -> Html State.Msg +users xs = + ul [] + (xs + |> List.map + (\x -> + li [ [ "py-4", "flex" ] |> Tailwind.use |> class ] + [ p [ [ "flex-1" ] |> Tailwind.use |> class ] [ text x ] + , div [ [ "flex-1" ] |> Tailwind.use |> class ] + [ UI.simpleButton + { label = "Delete" + , handleClick = State.AttemptDeleteAccount x + } + ] + ] + ) + ) + + +render : State.Model -> Html State.Msg +render model = + div + [ [ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + |> class + ] + [ UI.header 2 "Welcome!" + , div [] + [ UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + ] + , div [ [ "py-3" ] |> Tailwind.use |> class ] + [ case model.adminTab of + State.Accounts -> + UI.textButton + { label = "Switch to trips" + , handleClick = State.UpdateAdminTab State.Trips + } + + State.Trips -> + UI.textButton + { label = "Switch to accounts" + , handleClick = State.UpdateAdminTab State.Accounts + } + ] + , case model.adminTab of + State.Accounts -> + div [] + [ inviteUser model + , allUsers model + ] + + State.Trips -> + allTrips model + , Common.allErrors model + ] diff --git a/users/wpcarro/assessments/tt/client/src/Common.elm b/users/wpcarro/assessments/tt/client/src/Common.elm new file mode 100644 index 000000000000..63ba97b794ac --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Common.elm @@ -0,0 +1,37 @@ +module Common exposing (..) + +import Html exposing (..) +import Maybe.Extra as ME +import State +import UI +import Utils + + +allErrors : State.Model -> Html State.Msg +allErrors model = + div [] + (State.allErrors + model + |> List.map + (\( mError, title ) -> + case mError of + Nothing -> + text "" + + Just err -> + UI.errorBanner + { title = title + , body = Utils.explainHttpError err + } + ) + ) + + +withSession : State.Model -> (State.Session -> Html State.Msg) -> Html State.Msg +withSession model renderWithSession = + case model.session of + Nothing -> + div [] [ UI.paragraph "You need a valid session to view this page. Please attempt to log in." ] + + Just session -> + renderWithSession session diff --git a/users/wpcarro/assessments/tt/client/src/Login.elm b/users/wpcarro/assessments/tt/client/src/Login.elm new file mode 100644 index 000000000000..b1a436098afd --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Login.elm @@ -0,0 +1,199 @@ +module Login exposing (render) + +import Common +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State +import Tailwind +import UI +import Utils + + +googleSignIn : Html State.Msg +googleSignIn = + div + [ class "g-signin2" + , attribute "onsuccess" "onSignIn" + , onClick State.GoogleSignIn + ] + [] + + +loginForm : State.Model -> Html State.Msg +loginForm model = + div + [ [ "w-full" + , "max-w-xs" + , "mx-auto" + ] + |> Tailwind.use + |> class + ] + [ div + [ [ "bg-white" + , "shadow-md" + , "rounded" + , "px-8" + , "pt-6" + , "pb-8" + , "mb-4" + , "text-left" + ] + |> Tailwind.use + |> class + ] + [ div [ [ "text-center", "pb-6" ] |> Tailwind.use |> class ] + [ UI.textButton + { handleClick = State.ToggleLoginForm + , label = + case model.loginTab of + State.LoginForm -> + "Switch to sign up" + + State.SignUpForm -> + "Switch to login" + } + ] + , div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "username", text_ = "Username" } + , UI.textField + { inputId = "Username" + , pholder = "Username" + , handleInput = State.UpdateUsername + , inputValue = model.username + } + ] + , case model.loginTab of + State.LoginForm -> + text "" + + State.SignUpForm -> + div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "email", text_ = "Email" } + , input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id "email" + , placeholder "who@domain.tld" + , onInput State.UpdateEmail + ] + [] + ] + , div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "password", text_ = "Password" } + , input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id "password" + , type_ "password" + , placeholder "******************" + , onInput State.UpdatePassword + ] + [] + ] + , case model.loginTab of + State.LoginForm -> + div [ [ "flex", "space-around" ] |> Tailwind.use |> class ] + [ UI.simpleButton + { handleClick = State.AttemptLogin + , label = "Login" + } + , div [ [ "pl-4" ] |> Tailwind.use |> class ] [ googleSignIn ] + ] + + State.SignUpForm -> + if + List.all identity + [ String.length model.username > 0 + , String.length model.email > 0 + , String.length model.password > 0 + ] + then + div [] + [ UI.simpleButton + { handleClick = State.AttemptSignUp + , label = "Sign up" + } + ] + + else + UI.disabledButton { label = "Sign up" } + ] + ] + + +login : + State.Model + -> Html State.Msg +login model = + div + [ [ "text-center" + , "py-20" + , "bg-gray-200" + , "h-screen" + ] + |> Tailwind.use + |> class + ] + [ UI.header 3 "Welcome to Trip Planner" + , loginForm model + , Common.allErrors model + ] + + +logout : State.Model -> Html State.Msg +logout model = + div + [ [ "text-center" + , "py-20" + , "bg-gray-200" + , "h-screen" + ] + |> Tailwind.use + |> class + ] + [ UI.header 3 "Looks like you're already signed in..." + , UI.simpleButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + , Common.allErrors model + ] + + +render : State.Model -> Html State.Msg +render model = + case model.session of + Nothing -> + login model + + Just x -> + logout model diff --git a/users/wpcarro/assessments/tt/client/src/Main.elm b/users/wpcarro/assessments/tt/client/src/Main.elm new file mode 100644 index 000000000000..de71a72db0df --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Main.elm @@ -0,0 +1,62 @@ +module Main exposing (main) + +import Admin +import Browser +import Html exposing (..) +import Login +import Manager +import State +import Url +import User + + +viewForRoute : State.Route -> (State.Model -> Html State.Msg) +viewForRoute route = + case route of + State.Login -> + Login.render + + State.UserHome -> + User.render + + State.ManagerHome -> + Manager.render + + State.AdminHome -> + Admin.render + + +view : State.Model -> Browser.Document State.Msg +view model = + { title = "TripPlanner" + , body = + [ case ( model.session, model.route ) of + -- Redirect to /login when someone is not authenticated. + -- TODO(wpcarro): We should ensure that /login shows in the URL + -- bar. + ( Nothing, _ ) -> + Login.render model + + ( Just session, Nothing ) -> + Login.render model + + -- Authenticated + ( Just session, Just route ) -> + if State.isAuthorized session.role route then + viewForRoute route model + + else + text "Access denied. You are not authorized to be here. Evacuate the area immediately" + ] + } + + +main = + Browser.application + { init = State.init + , onUrlChange = State.UrlChanged + , onUrlRequest = State.LinkClicked + , subscriptions = \_ -> Sub.none + , update = State.update + , view = view + } diff --git a/users/wpcarro/assessments/tt/client/src/Manager.elm b/users/wpcarro/assessments/tt/client/src/Manager.elm new file mode 100644 index 000000000000..cd15c99a34a8 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Manager.elm @@ -0,0 +1,70 @@ +module Manager exposing (render) + +import Array +import Common +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import RemoteData +import State +import Tailwind +import UI +import Utils + + +allUsers : State.Model -> Html State.Msg +allUsers model = + case model.accounts of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetAccounts } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\account -> + li [] + [ UI.paragraph + (account.username + ++ " - " + ++ State.roleToString account.role + ) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteAccount account.username + } + ] + ) + ) + + +render : State.Model -> Html State.Msg +render model = + Common.withSession model + (\session -> + div + [ class + ([ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + ) + ] + [ h1 [] + [ UI.header 2 ("Welcome back, " ++ session.username ++ "!") + , UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + , allUsers model + , Common.allErrors model + ] + ] + ) diff --git a/users/wpcarro/assessments/tt/client/src/Shared.elm b/users/wpcarro/assessments/tt/client/src/Shared.elm new file mode 100644 index 000000000000..addb0a4ffd12 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Shared.elm @@ -0,0 +1,7 @@ +module Shared exposing (..) + +clientOrigin = + "http://localhost:8000" + +serverOrigin = + "http://localhost:3000" diff --git a/users/wpcarro/assessments/tt/client/src/State.elm b/users/wpcarro/assessments/tt/client/src/State.elm new file mode 100644 index 000000000000..b3f78bb16980 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/State.elm @@ -0,0 +1,1014 @@ +port module State exposing (..) + +import Array exposing (Array) +import Browser +import Browser.Navigation as Nav +import Date +import DatePicker +import Http +import Json.Decode as JD +import Json.Decode.Extra as JDE +import Json.Encode as JE +import Json.Encode.Extra as JEE +import Process +import RemoteData exposing (WebData) +import Shared +import Task +import Time +import Url +import Url.Builder as UrlBuilder +import Url.Parser exposing ((</>), Parser, int, map, oneOf, s, string) +import Utils + + + +-------------------------------------------------------------------------------- +-- Types +-------------------------------------------------------------------------------- + + +type Msg + = DoNothing + | UpdateUsername String + | UpdateEmail String + | UpdatePassword String + | UpdateRole String + | UpdateAdminTab AdminTab + | UpdateTripDestination String + | UpdateTripStartDate DatePicker.Msg + | UpdateTripEndDate DatePicker.Msg + | UpdateTripComment String + | UpdateEditTripDestination String + | UpdateEditTripComment String + | ClearErrors + | ToggleLoginForm + | PrintPage + | GoogleSignIn + | GoogleSignOut + | UpdateInviteEmail String + | UpdateInviteRole (Maybe Role) + | ReceiveTodaysDate Date.Date + | EditTrip Trip + | CancelEditTrip + -- SPA + | LinkClicked Browser.UrlRequest + | UrlChanged Url.Url + -- Outbound network + | AttemptGetAccounts + | AttemptGetTrips + | AttemptSignUp + | AttemptLogin + | AttemptLogout + | AttemptDeleteAccount String + | AttemptCreateTrip Date.Date Date.Date + | AttemptDeleteTrip Trip + | AttemptInviteUser Role + | AttemptUpdateTrip TripPK Trip + -- Inbound network + | GotAccounts (WebData (List Account)) + | GotTrips (WebData (List Trip)) + | GotSignUp (Result Http.Error Session) + | GotLogin (Result Http.Error Session) + | GotLogout (Result Http.Error String) + | GotDeleteAccount (Result Http.Error String) + | GotCreateTrip (Result Http.Error ()) + | GotDeleteTrip (Result Http.Error ()) + | GotInviteUser (Result Http.Error ()) + | GotUpdateTrip (Result Http.Error ()) + + +type Route + = Login + | UserHome + | ManagerHome + | AdminHome + + +type Role + = User + | Manager + | Admin + + +type alias Account = + { username : String + , role : Role + } + + +type alias Session = + { role : Role + , username : String + } + + +type alias Review = + { rowid : Int + , content : String + , rating : Int + , user : String + , dateOfVisit : String + } + + +type AdminTab + = Accounts + | Trips + + +type LoginTab + = LoginForm + | SignUpForm + + +type alias Trip = + { username : String + , destination : String + , startDate : Date.Date + , endDate : Date.Date + , comment : String + } + + +type alias TripPK = + { username : String + , destination : String + , startDate : Date.Date + } + + +type alias Model = + { route : Maybe Route + , url : Url.Url + , key : Nav.Key + , session : Maybe Session + , todaysDate : Maybe Date.Date + , username : String + , email : String + , password : String + , role : Maybe Role + , accounts : WebData (List Account) + , startDatePicker : DatePicker.DatePicker + , endDatePicker : DatePicker.DatePicker + , tripDestination : String + , tripStartDate : Maybe Date.Date + , tripEndDate : Maybe Date.Date + , tripComment : String + , trips : WebData (List Trip) + , editingTrip : Maybe Trip + , editTripDestination : String + , editTripComment : String + , adminTab : AdminTab + , loginTab : LoginTab + , inviteEmail : String + , inviteRole : Maybe Role + , inviteResponseStatus : WebData () + , updateTripStatus : WebData () + , loginError : Maybe Http.Error + , logoutError : Maybe Http.Error + , signUpError : Maybe Http.Error + , deleteUserError : Maybe Http.Error + , createTripError : Maybe Http.Error + , deleteTripError : Maybe Http.Error + , inviteUserError : Maybe Http.Error + } + + +allErrors : Model -> List ( Maybe Http.Error, String ) +allErrors model = + [ ( model.loginError, "Error attempting to authenticate" ) + , ( model.logoutError, "Error attempting to log out" ) + , ( model.signUpError, "Error attempting to create your account" ) + , ( model.deleteUserError, "Error attempting to delete a user" ) + , ( model.createTripError, "Error attempting to create a trip" ) + , ( model.inviteUserError, "Error attempting to invite a user" ) + ] + + + +-------------------------------------------------------------------------------- +-- Functions +-------------------------------------------------------------------------------- + + +roleToString : Role -> String +roleToString role = + case role of + User -> + "user" + + Manager -> + "manager" + + Admin -> + "admin" + + +endpoint : List String -> List UrlBuilder.QueryParameter -> String +endpoint = + UrlBuilder.crossOrigin Shared.serverOrigin + + +encodeRole : Role -> JE.Value +encodeRole x = + case x of + User -> + JE.string "user" + + Manager -> + JE.string "manager" + + Admin -> + JE.string "admin" + + +decodeRole : JD.Decoder Role +decodeRole = + let + toRole : String -> JD.Decoder Role + toRole s = + case s of + "user" -> + JD.succeed User + + "manager" -> + JD.succeed Manager + + "admin" -> + JD.succeed Admin + + x -> + JD.fail ("Invalid input: " ++ x) + in + JD.string |> JD.andThen toRole + + +decodeSession : JD.Decoder Session +decodeSession = + JD.map2 + Session + (JD.field "role" decodeRole) + (JD.field "username" JD.string) + + +encodeLoginRequest : String -> String -> JE.Value +encodeLoginRequest username password = + JE.object + [ ( "username", JE.string username ) + , ( "password", JE.string password ) + ] + + +login : String -> String -> Cmd Msg +login username password = + Utils.postWithCredentials + { url = endpoint [ "login" ] [] + , body = Http.jsonBody (encodeLoginRequest username password) + , expect = Http.expectJson GotLogin decodeSession + } + + +logout : Cmd Msg +logout = + Utils.getWithCredentials + { url = endpoint [ "logout" ] [] + , expect = Http.expectString GotLogout + } + + +signUp : + { username : String + , email : String + , password : String + } + -> Cmd Msg +signUp { username, email, password } = + Utils.postWithCredentials + { url = endpoint [ "accounts" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "email", JE.string username ) + , ( "password", JE.string password ) + , ( "role", JE.string "user" ) + ] + ) + , expect = Http.expectJson GotSignUp decodeSession + } + + +updateTrip : TripPK -> Trip -> Cmd Msg +updateTrip tripKey trip = + Utils.putWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "tripKey", encodeTripKey tripKey ) + , ( "destination", JE.string trip.destination ) + , ( "startDate", encodeDate trip.startDate ) + , ( "endDate", encodeDate trip.endDate ) + , ( "comment", JE.string trip.comment ) + ] + ) + , expect = Http.expectWhatever GotUpdateTrip + } + + +inviteUser : { email : String, role : Role } -> Cmd Msg +inviteUser { email, role } = + Utils.postWithCredentials + { url = endpoint [ "invite" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "email", JE.string email ) + , ( "role", encodeRole role ) + ] + ) + , expect = Http.expectWhatever GotInviteUser + } + + +createTrip : + { username : String + , destination : String + , startDate : Date.Date + , endDate : Date.Date + , comment : String + } + -> Cmd Msg +createTrip { username, destination, startDate, endDate, comment } = + Utils.postWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "destination", JE.string destination ) + , ( "startDate", encodeDate startDate ) + , ( "endDate", encodeDate endDate ) + , ( "comment", JE.string comment ) + ] + ) + , expect = Http.expectWhatever GotCreateTrip + } + + +deleteTrip : + { username : String + , destination : String + , startDate : Date.Date + } + -> Cmd Msg +deleteTrip { username, destination, startDate } = + Utils.deleteWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "destination", JE.string destination ) + , ( "startDate", encodeDate startDate ) + ] + ) + , expect = Http.expectWhatever GotDeleteTrip + } + + +deleteAccount : String -> Cmd Msg +deleteAccount username = + Utils.deleteWithCredentials + { url = endpoint [ "accounts" ] [ UrlBuilder.string "username" username ] + , body = Http.emptyBody + , expect = Http.expectString GotDeleteAccount + } + + +decodeReview : JD.Decoder Review +decodeReview = + JD.map5 + Review + (JD.field "rowid" JD.int) + (JD.field "content" JD.string) + (JD.field "rating" JD.int) + (JD.field "user" JD.string) + (JD.field "timestamp" JD.string) + + +encodeTripKey : TripPK -> JE.Value +encodeTripKey tripKey = + JE.object + [ ( "username", JE.string tripKey.username ) + , ( "destination", JE.string tripKey.destination ) + , ( "startDate", encodeDate tripKey.startDate ) + ] + + +encodeDate : Date.Date -> JE.Value +encodeDate date = + date |> Date.toIsoString |> JE.string + + +decodeDate : JD.Decoder Date.Date +decodeDate = + JD.string |> JD.andThen (Date.fromIsoString >> JDE.fromResult) + + +fetchTrips : Cmd Msg +fetchTrips = + Utils.getWithCredentials + { url = endpoint [ "trips" ] [] + , expect = + Http.expectJson + (RemoteData.fromResult >> GotTrips) + (JD.list + (JD.map5 + Trip + (JD.field "username" JD.string) + (JD.field "destination" JD.string) + (JD.field "startDate" decodeDate) + (JD.field "endDate" decodeDate) + (JD.field "comment" JD.string) + ) + ) + } + + +fetchAccounts : Cmd Msg +fetchAccounts = + Utils.getWithCredentials + { url = endpoint [ "accounts" ] [] + , expect = + Http.expectJson + (RemoteData.fromResult >> GotAccounts) + (JD.list + (JD.map2 + Account + (JD.field "username" JD.string) + (JD.field "role" decodeRole) + ) + ) + } + + +sleepAndClearErrors : Cmd Msg +sleepAndClearErrors = + Process.sleep 4000 + |> Task.perform (\_ -> ClearErrors) + + +isAuthorized : Role -> Route -> Bool +isAuthorized role route = + case ( role, route ) of + ( User, _ ) -> + True + + ( Manager, _ ) -> + True + + ( Admin, _ ) -> + True + + +homeRouteForRole : Role -> String +homeRouteForRole role = + case role of + User -> + "/user" + + Manager -> + "/manager" + + Admin -> + "/admin" + + +routeParser : Parser (Route -> a) a +routeParser = + oneOf + [ map Login (s "topic") + , map UserHome (s "user") + , map ManagerHome (s "manager") + , map AdminHome (s "admin") + ] + + +{-| Set init to `prod` when going live. +-} +prod : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +prod _ url key = + let + ( startDatePicker, startDatePickerCmd ) = + DatePicker.init + + ( endDatePicker, endDatePickerCmd ) = + DatePicker.init + in + ( { route = Nothing + , url = url + , key = key + , session = Nothing + , todaysDate = Nothing + , username = "" + , email = "" + , password = "" + , role = Nothing + , accounts = RemoteData.NotAsked + , tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + , trips = RemoteData.NotAsked + , editingTrip = Nothing + , editTripDestination = "" + , editTripComment = "" + , startDatePicker = startDatePicker + , endDatePicker = endDatePicker + , adminTab = Accounts + , loginTab = LoginForm + , inviteEmail = "" + , inviteRole = Nothing + , inviteResponseStatus = RemoteData.NotAsked + , updateTripStatus = RemoteData.NotAsked + , loginError = Nothing + , logoutError = Nothing + , signUpError = Nothing + , deleteUserError = Nothing + , createTripError = Nothing + , deleteTripError = Nothing + , inviteUserError = Nothing + } + , Cmd.batch + [ Cmd.map UpdateTripStartDate startDatePickerCmd + , Cmd.map UpdateTripEndDate endDatePickerCmd + , Date.today |> Task.perform ReceiveTodaysDate + ] + ) + + +{-| When working on a feature for the UserHome, use this. +-} +userHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +userHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just UserHome + , session = Just { username = "mimi", role = User } + , trips = + RemoteData.Success + [ { username = "mimi" + , destination = "Barcelona" + , startDate = Date.fromCalendarDate 2020 Time.Sep 25 + , endDate = Date.fromCalendarDate 2020 Time.Oct 5 + , comment = "Blah" + } + , { username = "mimi" + , destination = "Paris" + , startDate = Date.fromCalendarDate 2021 Time.Jan 1 + , endDate = Date.fromCalendarDate 2021 Time.Feb 1 + , comment = "Bon voyage!" + } + ] + } + , cmd + ) + + +managerHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +managerHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just ManagerHome + , session = Just { username = "bill", role = Manager } + } + , cmd + ) + + +adminHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +adminHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just AdminHome + , session = Just { username = "wpcarro", role = Admin } + } + , cmd + ) + + +port printPage : () -> Cmd msg + + +port googleSignIn : () -> Cmd msg + + +port googleSignOut : () -> Cmd msg + + +{-| The initial state for the application. +-} +init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +init flags url key = + prod flags url key + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + DoNothing -> + ( model, Cmd.none ) + + UpdateUsername x -> + ( { model | username = x }, Cmd.none ) + + UpdatePassword x -> + ( { model | password = x }, Cmd.none ) + + UpdateEmail x -> + ( { model | email = x }, Cmd.none ) + + UpdateAdminTab x -> + ( { model | adminTab = x }, Cmd.none ) + + UpdateRole x -> + let + maybeRole = + case x of + "user" -> + Just User + + "manager" -> + Just Manager + + "admin" -> + Just Admin + + _ -> + Nothing + in + ( { model | role = maybeRole }, Cmd.none ) + + UpdateTripDestination x -> + ( { model | tripDestination = x }, Cmd.none ) + + UpdateTripStartDate dpMsg -> + let + ( newDatePicker, dateEvent ) = + DatePicker.update DatePicker.defaultSettings dpMsg model.startDatePicker + + newDate = + case dateEvent of + DatePicker.Picked changedDate -> + Just changedDate + + _ -> + model.tripStartDate + in + ( { model + | tripStartDate = newDate + , startDatePicker = newDatePicker + } + , Cmd.none + ) + + UpdateTripEndDate dpMsg -> + let + ( newDatePicker, dateEvent ) = + DatePicker.update DatePicker.defaultSettings dpMsg model.endDatePicker + + newDate = + case dateEvent of + DatePicker.Picked changedDate -> + Just changedDate + + _ -> + model.tripEndDate + in + ( { model + | tripEndDate = newDate + , endDatePicker = newDatePicker + } + , Cmd.none + ) + + UpdateTripComment x -> + ( { model | tripComment = x }, Cmd.none ) + + UpdateEditTripDestination x -> + ( { model | editTripDestination = x }, Cmd.none ) + + UpdateEditTripComment x -> + ( { model | editTripComment = x }, Cmd.none ) + + ClearErrors -> + ( { model + | loginError = Nothing + , logoutError = Nothing + , signUpError = Nothing + , deleteUserError = Nothing + , createTripError = Nothing + } + , Cmd.none + ) + + ToggleLoginForm -> + ( { model + | loginTab = + case model.loginTab of + LoginForm -> + SignUpForm + + SignUpForm -> + LoginForm + } + , Cmd.none + ) + + PrintPage -> + ( model, printPage () ) + + GoogleSignIn -> + ( model, googleSignIn () ) + + GoogleSignOut -> + ( model, googleSignOut () ) + + UpdateInviteEmail x -> + ( { model | inviteEmail = x }, Cmd.none ) + + UpdateInviteRole mRole -> + ( { model | inviteRole = mRole }, Cmd.none ) + + ReceiveTodaysDate date -> + ( { model | todaysDate = Just date }, Cmd.none ) + + EditTrip trip -> + ( { model + | editingTrip = Just trip + , editTripDestination = trip.destination + , editTripComment = trip.comment + } + , Cmd.none + ) + + CancelEditTrip -> + ( { model + | editingTrip = Nothing + , editTripDestination = "" + , editTripComment = "" + } + , Cmd.none + ) + + LinkClicked urlRequest -> + case urlRequest of + Browser.Internal url -> + ( model, Nav.pushUrl model.key (Url.toString url) ) + + Browser.External href -> + ( model, Nav.load href ) + + UrlChanged url -> + let + route = + Url.Parser.parse routeParser url + in + case route of + Just UserHome -> + ( { model + | url = url + , route = route + , trips = RemoteData.Loading + } + , fetchTrips + ) + + Just ManagerHome -> + ( { model + | url = url + , route = route + , accounts = RemoteData.Loading + } + , fetchAccounts + ) + + Just AdminHome -> + ( { model + | url = url + , route = route + , accounts = RemoteData.Loading + , trips = RemoteData.Loading + } + , Cmd.batch + [ fetchAccounts + , fetchTrips + ] + ) + + _ -> + ( { model + | url = url + , route = route + } + , Cmd.none + ) + + -- GET /accounts + AttemptGetAccounts -> + ( { model | accounts = RemoteData.Loading }, fetchAccounts ) + + GotAccounts xs -> + ( { model | accounts = xs }, Cmd.none ) + + -- DELETE /accounts + AttemptDeleteAccount username -> + ( model, deleteAccount username ) + + GotDeleteAccount result -> + case result of + Ok _ -> + ( model, fetchAccounts ) + + Err e -> + ( { model | deleteUserError = Just e } + , sleepAndClearErrors + ) + + -- POST /trips + AttemptCreateTrip startDate endDate -> + ( model + , case model.session of + Nothing -> + Cmd.none + + Just session -> + createTrip + { username = session.username + , destination = model.tripDestination + , startDate = startDate + , endDate = endDate + , comment = model.tripComment + } + ) + + GotCreateTrip result -> + case result of + Ok _ -> + ( { model + | tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + } + , fetchTrips + ) + + Err e -> + ( { model + | createTripError = Just e + , tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + } + , sleepAndClearErrors + ) + + -- DELETE /trips + AttemptDeleteTrip trip -> + ( model + , deleteTrip + { username = trip.username + , destination = trip.destination + , startDate = trip.startDate + } + ) + + GotDeleteTrip result -> + case result of + Ok _ -> + ( model, fetchTrips ) + + Err e -> + ( { model | deleteTripError = Just e } + , sleepAndClearErrors + ) + + AttemptInviteUser role -> + ( { model | inviteResponseStatus = RemoteData.Loading } + , inviteUser + { email = model.inviteEmail + , role = role + } + ) + + GotInviteUser result -> + case result of + Ok _ -> + ( { model + | inviteEmail = "" + , inviteRole = Nothing + , inviteResponseStatus = RemoteData.Success () + } + , Cmd.none + ) + + Err e -> + ( { model + | inviteUserError = Just e + , inviteResponseStatus = RemoteData.Failure e + } + , sleepAndClearErrors + ) + + -- PATCH /trips + AttemptUpdateTrip tripKey trip -> + ( { model | updateTripStatus = RemoteData.Loading } + , updateTrip tripKey trip + ) + + GotUpdateTrip result -> + case result of + Ok _ -> + ( { model | updateTripStatus = RemoteData.Success () } + , fetchTrips + ) + + Err e -> + ( { model | updateTripStatus = RemoteData.Failure e } + , Cmd.none + ) + + -- POST /accounts + AttemptSignUp -> + ( model + , signUp + { username = model.username + , email = model.email + , password = model.password + } + ) + + GotSignUp result -> + case result of + Ok session -> + ( { model | session = Just session } + , Nav.pushUrl model.key (homeRouteForRole session.role) + ) + + Err x -> + ( { model | signUpError = Just x } + , sleepAndClearErrors + ) + + -- GET /trips + AttemptGetTrips -> + ( { model | trips = RemoteData.Loading }, fetchTrips ) + + GotTrips xs -> + ( { model | trips = xs }, Cmd.none ) + + -- POST /login + AttemptLogin -> + ( model, login model.username model.password ) + + GotLogin result -> + case result of + Ok session -> + ( { model | session = Just session } + , Nav.pushUrl model.key (homeRouteForRole session.role) + ) + + Err x -> + ( { model | loginError = Just x } + , sleepAndClearErrors + ) + + -- GET /logout + AttemptLogout -> + ( model, logout ) + + GotLogout result -> + case result of + Ok _ -> + ( { model | session = Nothing } + , Nav.pushUrl model.key "/login" + ) + + Err e -> + ( { model | logoutError = Just e } + , sleepAndClearErrors + ) diff --git a/users/wpcarro/assessments/tt/client/src/Tailwind.elm b/users/wpcarro/assessments/tt/client/src/Tailwind.elm new file mode 100644 index 000000000000..57d419db5a82 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Tailwind.elm @@ -0,0 +1,29 @@ +module Tailwind exposing (..) + +{-| Functions to make Tailwind development in Elm even more pleasant. +-} + + +{-| Conditionally use `class` selection when `condition` is true. +-} +when : Bool -> String -> String +when condition class = + if condition then + class + + else + "" + + +if_ : Bool -> String -> String -> String +if_ condition whenTrue whenFalse = + if condition then + whenTrue + + else + whenFalse + + +use : List String -> String +use styles = + String.join " " styles diff --git a/users/wpcarro/assessments/tt/client/src/UI.elm b/users/wpcarro/assessments/tt/client/src/UI.elm new file mode 100644 index 000000000000..7f8f379795f7 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/UI.elm @@ -0,0 +1,318 @@ +module UI exposing (..) + +import Date +import DatePicker exposing (defaultSettings) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State +import Tailwind + + +label_ : { for_ : String, text_ : String } -> Html msg +label_ { for_, text_ } = + label + [ [ "block" + , "text-gray-700" + , "text-sm" + , "font-bold" + , "mb-2" + ] + |> Tailwind.use + |> class + , for for_ + ] + [ text text_ ] + + +errorBanner : { title : String, body : String } -> Html msg +errorBanner { title, body } = + div + [ [ "text-left" + , "fixed" + , "container" + , "top-0" + , "mt-6" + ] + |> Tailwind.use + |> class + , style "left" "50%" + + -- TODO(wpcarro): Consider supporting breakpoints, but for now + -- don't. + , style "width" "800px" + , style "margin-left" "-400px" + ] + [ div + [ [ "bg-red-500" + , "text-white" + , "font-bold" + , "rounded-t" + , "px-4" + , "py-2" + ] + |> Tailwind.use + |> class + ] + [ text title ] + , div + [ [ "border" + , "border-t-0" + , "border-red-400" + , "rounded-b" + , "bg-red-100" + , "px-4" + , "py-3" + , "text-red-700" + ] + |> Tailwind.use + |> class + ] + [ p [] [ text body ] ] + ] + + +baseButton : + { label : String + , enabled : Bool + , handleClick : msg + , extraClasses : List String + } + -> Html msg +baseButton { label, enabled, handleClick, extraClasses } = + button + [ [ if enabled then + "bg-blue-500" + + else + "bg-gray-500" + , if enabled then + "hover:bg-blue-700" + + else + "" + , if enabled then + "" + + else + "cursor-not-allowed" + , "text-white" + , "font-bold" + , "py-1" + , "shadow-lg" + , "px-4" + , "rounded" + , "focus:outline-none" + , "focus:shadow-outline" + ] + ++ extraClasses + |> Tailwind.use + |> class + , onClick handleClick + , disabled (not enabled) + ] + [ text label ] + + +simpleButton : + { label : String + , handleClick : msg + } + -> Html msg +simpleButton { label, handleClick } = + baseButton + { label = label + , enabled = True + , handleClick = handleClick + , extraClasses = [] + } + + +disabledButton : + { label : String } + -> Html State.Msg +disabledButton { label } = + baseButton + { label = label + , enabled = False + , handleClick = State.DoNothing + , extraClasses = [] + } + + +textButton : + { label : String + , handleClick : msg + } + -> Html msg +textButton { label, handleClick } = + button + [ [ "text-blue-600" + , "hover:text-blue-500" + , "font-bold" + , "hover:underline" + , "focus:outline-none" + ] + |> Tailwind.use + |> class + , onClick handleClick + ] + [ text label ] + + +textField : + { pholder : String + , inputId : String + , handleInput : String -> msg + , inputValue : String + } + -> Html msg +textField { pholder, inputId, handleInput, inputValue } = + input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id inputId + , value inputValue + , placeholder pholder + , onInput handleInput + ] + [] + + +toggleButton : + { toggled : Bool + , label : String + , handleEnable : msg + , handleDisable : msg + } + -> Html msg +toggleButton { toggled, label, handleEnable, handleDisable } = + button + [ [ if toggled then + "bg-blue-700" + + else + "bg-blue-500" + , "hover:bg-blue-700" + , "text-white" + , "font-bold" + , "py-2" + , "px-4" + , "rounded" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , onClick + (if toggled then + handleDisable + + else + handleEnable + ) + ] + [ text label ] + + +paragraph : String -> Html msg +paragraph x = + p [ [ "text-xl" ] |> Tailwind.use |> class ] [ text x ] + + +header : Int -> String -> Html msg +header which x = + let + hStyles = + case which of + 1 -> + [ "text-6xl" + , "py-12" + ] + + 2 -> + [ "text-3xl" + , "py-6" + ] + + _ -> + [ "text-2xl" + , "py-2" + ] + in + h1 + [ hStyles + ++ [ "font-bold" + , "text-gray-700" + ] + |> Tailwind.use + |> class + ] + [ text x ] + + +link : String -> String -> Html msg +link path label = + a + [ href path + , [ "underline" + , "text-blue-600" + , "text-xl" + ] + |> Tailwind.use + |> class + ] + [ text label ] + + +absentData : { handleFetch : msg } -> Html msg +absentData { handleFetch } = + div [] + [ paragraph "Welp... it looks like you've caught us in a state that we considered impossible: we did not fetch the data upon which this page depends. Maybe you can help us out by clicking the super secret, highly privileged \"Fetch data\" button below (we don't normally show people this)." + , div [ [ "py-4" ] |> Tailwind.use |> class ] + [ simpleButton + { label = "Fetch data" + , handleClick = handleFetch + } + ] + ] + + +datePicker : + { mDate : Maybe Date.Date + , prompt : String + , prefix : String + , picker : DatePicker.DatePicker + , onUpdate : DatePicker.Msg -> State.Msg + } + -> Html State.Msg +datePicker { mDate, prompt, prefix, picker, onUpdate } = + let + settings = + { defaultSettings + | placeholder = prompt + , inputClassList = + [ ( "text-center", True ) + , ( "py-2", True ) + ] + } + in + div [ [ "w-1/2", "py-4", "mx-auto" ] |> Tailwind.use |> class ] + [ DatePicker.view mDate settings picker |> Html.map onUpdate ] + + +wrapNoPrint : Html State.Msg -> Html State.Msg +wrapNoPrint component = + div [ [ "no-print" ] |> Tailwind.use |> class ] [ component ] diff --git a/users/wpcarro/assessments/tt/client/src/User.elm b/users/wpcarro/assessments/tt/client/src/User.elm new file mode 100644 index 000000000000..87871b78dbc4 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/User.elm @@ -0,0 +1,245 @@ +module User exposing (render) + +import Common +import Date +import DatePicker +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Maybe.Extra as ME +import RemoteData +import State +import Tailwind +import UI +import Utils + + +createTrip : State.Model -> Html State.Msg +createTrip model = + div [] + [ UI.header 3 "Plan Upcoming Trip" + , UI.textField + { pholder = "Where are you going?" + , inputId = "destination" + , handleInput = State.UpdateTripDestination + , inputValue = model.tripDestination + } + , div [ [ "flex" ] |> Tailwind.use |> class ] + [ UI.datePicker + { mDate = model.tripStartDate + , prompt = "Set departure date" + , prefix = "Departure: " + , picker = model.startDatePicker + , onUpdate = State.UpdateTripStartDate + } + , UI.datePicker + { mDate = model.tripEndDate + , prompt = "Set return date" + , prefix = "Return: " + , picker = model.endDatePicker + , onUpdate = State.UpdateTripEndDate + } + ] + , UI.textField + { pholder = "Comments?" + , inputId = "comment" + , handleInput = State.UpdateTripComment + , inputValue = model.tripComment + } + , UI.baseButton + { enabled = + List.all + identity + [ String.length model.tripDestination > 0 + , String.length model.tripComment > 0 + , ME.isJust model.tripStartDate + , ME.isJust model.tripEndDate + ] + , extraClasses = [ "my-4" ] + , handleClick = + case ( model.tripStartDate, model.tripEndDate ) of + ( Nothing, _ ) -> + State.DoNothing + + ( _, Nothing ) -> + State.DoNothing + + ( Just startDate, Just endDate ) -> + State.AttemptCreateTrip startDate endDate + , label = "Schedule trip" + } + ] + + +renderEditTrip : State.Model -> State.Trip -> Html State.Msg +renderEditTrip model trip = + li [] + [ div [] + [ UI.textField + { handleInput = State.UpdateEditTripDestination + , inputId = "edit-trip-destination" + , inputValue = model.editTripDestination + , pholder = "Destination" + } + , UI.textField + { handleInput = State.UpdateEditTripComment + , inputId = "edit-trip-comment" + , inputValue = model.editTripComment + , pholder = "Comment" + } + ] + , div [] + [ UI.baseButton + { enabled = + case model.updateTripStatus of + RemoteData.Loading -> + False + + _ -> + True + , extraClasses = [] + , label = + case model.updateTripStatus of + RemoteData.Loading -> + "Saving..." + + _ -> + "Save" + , handleClick = + State.AttemptUpdateTrip + { username = trip.username + , destination = trip.destination + , startDate = trip.startDate + } + { username = trip.username + , destination = model.editTripDestination + , startDate = trip.startDate + , endDate = trip.endDate + , comment = model.editTripComment + } + } + , UI.simpleButton + { label = "Cancel" + , handleClick = State.CancelEditTrip + } + ] + ] + + +renderTrip : Date.Date -> State.Trip -> Html State.Msg +renderTrip today trip = + li + [ [ "py-2" ] + |> Tailwind.use + |> class + ] + [ if Date.compare today trip.startDate == GT then + UI.paragraph + (String.fromInt (Date.diff Date.Days trip.startDate today) + ++ " days until you're travelling to " + ++ trip.destination + ++ " for " + ++ String.fromInt + (Date.diff + Date.Days + trip.startDate + trip.endDate + ) + ++ " days." + ) + + else + UI.paragraph + (String.fromInt (Date.diff Date.Days today trip.endDate) + ++ " days ago you returned from your trip to " + ++ trip.destination + ) + , UI.paragraph ("\"" ++ trip.comment ++ "\"") + , UI.wrapNoPrint + (UI.textButton + { label = "Edit" + , handleClick = State.EditTrip trip + } + ) + , UI.wrapNoPrint + (UI.textButton + { label = "Delete" + , handleClick = State.AttemptDeleteTrip trip + } + ) + ] + + +trips : State.Model -> Html State.Msg +trips model = + div [] + [ UI.header 3 "Your Trips" + , case model.trips of + RemoteData.NotAsked -> + UI.paragraph "Somehow we've reached the user home page without requesting your trips data. Please report this to our engineering team at bugs@tripplaner.tld" + + RemoteData.Loading -> + UI.paragraph "Loading your trips..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + case model.todaysDate of + Nothing -> + text "" + + Just today -> + div [ [ "mb-10" ] |> Tailwind.use |> class ] + [ ul [ [ "my-4" ] |> Tailwind.use |> class ] + (xs + |> List.sortWith (\x y -> Date.compare y.startDate x.startDate) + |> List.map + (\trip -> + case model.editingTrip of + Nothing -> + renderTrip today trip + + Just x -> + if x == trip then + renderEditTrip model trip + + else + renderTrip today trip + ) + ) + , UI.wrapNoPrint + (UI.simpleButton + { label = "Print iternary" + , handleClick = State.PrintPage + } + ) + ] + ] + + +render : State.Model -> Html State.Msg +render model = + Common.withSession model + (\session -> + div + [ class + ([ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + ) + ] + [ UI.wrapNoPrint (UI.header 2 ("Welcome, " ++ session.username ++ "!")) + , UI.wrapNoPrint (createTrip model) + , trips model + , UI.wrapNoPrint + (UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + ) + , Common.allErrors model + ] + ) diff --git a/users/wpcarro/assessments/tt/client/src/Utils.elm b/users/wpcarro/assessments/tt/client/src/Utils.elm new file mode 100644 index 000000000000..60343cd87018 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Utils.elm @@ -0,0 +1,109 @@ +module Utils exposing (..) + +import DateFormat +import Http +import Time +import Shared + + +explainHttpError : Http.Error -> String +explainHttpError e = + case e of + Http.BadUrl _ -> + "Bad URL: you may have supplied an improperly formatted URL" + + Http.Timeout -> + "Timeout: the resource you requested did not arrive within the interval of time that you claimed it should" + + Http.BadStatus s -> + "Bad Status: the server returned a bad status code: " ++ String.fromInt s + + Http.BadBody b -> + "Bad Body: our application had trouble decoding the body of the response from the server: " ++ b + + Http.NetworkError -> + "Network Error: something went awry in the network stack. I recommend checking the server logs if you can." + + +getWithCredentials : + { url : String + , expect : Http.Expect msg + } + -> Cmd msg +getWithCredentials { url, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "GET" + , timeout = Nothing + , tracker = Nothing + , body = Http.emptyBody + , expect = expect + } + + +postWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +postWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "POST" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + + +deleteWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +deleteWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "DELETE" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + +putWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +putWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "PUT" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + + + +formatTime : Time.Posix -> String +formatTime ts = + DateFormat.format + [ DateFormat.monthNameFull + , DateFormat.text " " + , DateFormat.dayOfMonthSuffix + , DateFormat.text ", " + , DateFormat.yearNumber + ] + Time.utc + ts diff --git a/users/wpcarro/assessments/tt/data/accounts.csv b/users/wpcarro/assessments/tt/data/accounts.csv new file mode 100644 index 000000000000..f5fc77b6d77f --- /dev/null +++ b/users/wpcarro/assessments/tt/data/accounts.csv @@ -0,0 +1,2 @@ +mimi,$2b$12$LynoGCNbe2RA1WWSiBEMVudJKs5dxnssY16rYmUyiwlSBIhHBOLbu,miriamwright@google.com,user, +wpcarro,$2b$12$3wbi4xfQmksLsu6GOKTbj.5WHywESATnXB4R8FJ55RSRLy6X9xA7u,wpcarro@google.com,admin, \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/data/trips.csv b/users/wpcarro/assessments/tt/data/trips.csv new file mode 100644 index 000000000000..a583c750f77c --- /dev/null +++ b/users/wpcarro/assessments/tt/data/trips.csv @@ -0,0 +1,3 @@ +mimi,Rome,2020-08-10,2020-08-12,Heading home before the upcoming trip with Panarea. +mimi,Panarea,2020-08-15,2020-08-28,Exciting upcoming trip with Matt and Sarah! +mimi,London,2020-08-30,2020-09-15,Heading back to London... \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/populate.sqlite3 b/users/wpcarro/assessments/tt/populate.sqlite3 new file mode 100644 index 000000000000..e200d2b49c02 --- /dev/null +++ b/users/wpcarro/assessments/tt/populate.sqlite3 @@ -0,0 +1,7 @@ +PRAGMA foreign_keys = on; +.read src/init.sql +.mode csv +.import data/accounts.csv Accounts +.import data/trips.csv Trips +.mode column +.headers on \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/shell.nix b/users/wpcarro/assessments/tt/shell.nix new file mode 100644 index 000000000000..567b71060b7b --- /dev/null +++ b/users/wpcarro/assessments/tt/shell.nix @@ -0,0 +1,23 @@ +let + pkgs = import <nixpkgs> {}; + hailgun-src = builtins.fetchGit { + url = "https://bitbucket.org/echo_rm/hailgun.git"; + rev = "9d5da7c902b2399e0fcf3d494ee04cf2bbfe7c9e"; + }; + hailgun = pkgs.haskellPackages.callCabal2nix "hailgun" hailgun-src {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + (haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [ + hpkgs.servant-server + hpkgs.aeson + hpkgs.resource-pool + hpkgs.sqlite-simple + hpkgs.wai-cors + hpkgs.warp + hpkgs.cryptonite + hpkgs.uuid + hpkgs.envy + hailgun + ])) + ]; +} diff --git a/users/wpcarro/assessments/tt/src/.ghci b/users/wpcarro/assessments/tt/src/.ghci new file mode 100644 index 000000000000..efc88e630ccb --- /dev/null +++ b/users/wpcarro/assessments/tt/src/.ghci @@ -0,0 +1,2 @@ +:set prompt "> " +:set -Wall diff --git a/users/wpcarro/assessments/tt/src/API.hs b/users/wpcarro/assessments/tt/src/API.hs new file mode 100644 index 000000000000..471fa761e0f4 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/API.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +-------------------------------------------------------------------------------- +module API where +-------------------------------------------------------------------------------- +import Data.Text +import Servant.API +import Web.Cookie + +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Once authenticated, users receive a SessionCookie. +type SessionCookie = Header' '[Required] "Cookie" T.SessionCookie + +type API = + -- accounts: Create + "accounts" + :> Header "Cookie" T.SessionCookie + :> ReqBody '[JSON] T.CreateAccountRequest + :> Post '[JSON] NoContent + :<|> "verify" + :> ReqBody '[JSON] T.VerifyAccountRequest + :> Post '[JSON] NoContent + -- accounts: Read + -- accounts: Update + -- accounts: Delete + :<|> "accounts" + :> SessionCookie + :> QueryParam' '[Required] "username" Text + :> Delete '[JSON] NoContent + -- accounts: List + :<|> "accounts" + :> SessionCookie + :> Get '[JSON] [T.User] + + -- trips: Create + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.Trip + :> Post '[JSON] NoContent + -- trips: Read + -- trips: Update + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.UpdateTripRequest + :> Put '[JSON] NoContent + -- trips: Delete + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.TripPK + :> Delete '[JSON] NoContent + -- trips: List + :<|> "trips" + :> SessionCookie + :> Get '[JSON] [T.Trip] + + -- Miscellaneous + :<|> "login" + :> ReqBody '[JSON] T.AccountCredentials + :> Post '[JSON] (Headers '[Header "Set-Cookie" SetCookie] T.Session) + :<|> "logout" + :> SessionCookie + :> Get '[JSON] (Headers '[Header "Set-Cookie" SetCookie] NoContent) + :<|> "unfreeze" + :> SessionCookie + :> ReqBody '[JSON] T.UnfreezeAccountRequest + :> Post '[JSON] NoContent + :<|> "invite" + :> SessionCookie + :> ReqBody '[JSON] T.InviteUserRequest + :> Post '[JSON] NoContent + :<|> "accept-invitation" + :> ReqBody '[JSON] T.AcceptInvitationRequest + :> Post '[JSON] NoContent diff --git a/users/wpcarro/assessments/tt/src/Accounts.hs b/users/wpcarro/assessments/tt/src/Accounts.hs new file mode 100644 index 000000000000..c7ab7a2f135f --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Accounts.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Accounts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified PendingAccounts +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Delete the account in PendingAccounts and create on in Accounts. +transferFromPending :: FilePath -> T.PendingAccount -> IO () +transferFromPending dbFile T.PendingAccount{..} = withConnection dbFile $ + \conn -> withTransaction conn $ do + PendingAccounts.delete dbFile pendingAccountUsername + execute conn "INSERT INTO Accounts (username,password,email,role) VALUES (?,?,?,?)" + ( pendingAccountUsername + , pendingAccountPassword + , pendingAccountEmail + , pendingAccountRole + ) + +-- | Create a new account in the Accounts table. +create :: FilePath -> T.Username -> T.ClearTextPassword -> T.Email -> T.Role -> IO () +create dbFile username password email role = withConnection dbFile $ \conn -> do + hashed <- T.hashPassword password + execute conn "INSERT INTO Accounts (username,password,email,role) VALUES (?,?,?,?)" + (username, hashed, email, role) + +-- | Delete `username` from `dbFile`. +delete :: FilePath -> T.Username -> IO () +delete dbFile username = withConnection dbFile $ \conn -> do + execute conn "DELETE FROM Accounts WHERE username = ?" + (Only username) + +-- | Attempt to find `username` in the Account table of `dbFile`. +lookup :: FilePath -> T.Username -> IO (Maybe T.Account) +lookup dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,password,email,role,profilePicture FROM Accounts WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Return a list of accounts with the sensitive data removed. +list :: FilePath -> IO [T.User] +list dbFile = withConnection dbFile $ \conn -> do + accounts <- query_ conn "SELECT username,password,email,role,profilePicture FROM Accounts" + pure $ T.userFromAccount <$> accounts diff --git a/users/wpcarro/assessments/tt/src/App.hs b/users/wpcarro/assessments/tt/src/App.hs new file mode 100644 index 000000000000..742bc962dc55 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/App.hs @@ -0,0 +1,270 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeApplications #-} +-------------------------------------------------------------------------------- +module App where +-------------------------------------------------------------------------------- +import Control.Monad.IO.Class (liftIO) +import Data.String.Conversions (cs) +import Data.Text (Text) +import Servant +import API +import Utils +import Web.Cookie + +import qualified Network.Wai.Handler.Warp as Warp +import qualified Network.Wai.Middleware.Cors as Cors +import qualified System.Random as Random +import qualified Email as Email +import qualified Data.UUID as UUID +import qualified Types as T +import qualified Accounts as Accounts +import qualified Auth as Auth +import qualified Trips as Trips +import qualified Sessions as Sessions +import qualified Invitations as Invitations +import qualified LoginAttempts as LoginAttempts +import qualified PendingAccounts as PendingAccounts +-------------------------------------------------------------------------------- + +err429 :: ServerError +err429 = ServerError + { errHTTPCode = 429 + , errReasonPhrase = "Too many requests" + , errBody = "" + , errHeaders = [] + } + +-- | Send an email to recipient, `to`, with a secret code. +sendVerifyEmail :: T.Config + -> T.Username + -> T.Email + -> T.RegistrationSecret + -> IO (Either Email.SendError Email.SendSuccess) +sendVerifyEmail T.Config{..} (T.Username username) email (T.RegistrationSecret secretUUID) = do + Email.send mailgunAPIKey subject (cs body) email + where + subject = "Please confirm your account" + body = + let secret = secretUUID |> UUID.toString in + "To verify your account: POST /verify username=" ++ cs username ++ " secret=" ++ secret + +-- | Send an invitation email to recipient, `to`, with a secret code. +sendInviteEmail :: T.Config + -> T.Email + -> T.InvitationSecret + -> IO (Either Email.SendError Email.SendSuccess) +sendInviteEmail T.Config{..} email@(T.Email to) (T.InvitationSecret secretUUID) = do + Email.send mailgunAPIKey subject (cs body) email + where + subject = "You've been invited!" + body = + let secret = secretUUID |> UUID.toString in + "To accept the invitation: POST /accept-invitation username=<username> password=<password> email=" ++ cs to ++ " secret=" ++ secret + +server :: T.Config -> Server API +server config@T.Config{..} = createAccount + :<|> verifyAccount + :<|> deleteAccount + :<|> listAccounts + :<|> createTrip + :<|> updateTrip + :<|> deleteTrip + :<|> listTrips + :<|> login + :<|> logout + :<|> unfreezeAccount + :<|> inviteUser + :<|> acceptInvitation + where + -- Admit Admins + whatever the predicate `p` passes. + adminsAnd cookie p = Auth.assert dbFile cookie (\acct@T.Account{..} -> accountRole == T.Admin || p acct) + -- Admit Admins only. + adminsOnly cookie = adminsAnd cookie (const True) + + -- TODO(wpcarro): Handle failed CONSTRAINTs instead of sending 500s + createAccount :: Maybe T.SessionCookie + -> T.CreateAccountRequest + -> Handler NoContent + createAccount mCookie T.CreateAccountRequest{..} = + case (mCookie, createAccountRequestRole) of + (_, T.RegularUser) -> + doCreateAccount + (Nothing, T.Manager) -> + throwError err401 { errBody = "Only admins can create Manager accounts" } + (Nothing, T.Admin) -> + throwError err401 { errBody = "Only admins can create Admin accounts" } + (Just cookie, _) -> + adminsAnd cookie (\T.Account{..} -> accountRole == T.Manager) doCreateAccount + where + doCreateAccount :: Handler NoContent + doCreateAccount = do + secretUUID <- liftIO $ T.RegistrationSecret <$> Random.randomIO + liftIO $ PendingAccounts.create dbFile + secretUUID + createAccountRequestUsername + createAccountRequestPassword + createAccountRequestRole + createAccountRequestEmail + res <- liftIO $ sendVerifyEmail config + createAccountRequestUsername + createAccountRequestEmail + secretUUID + case res of + Left _ -> undefined + Right _ -> pure NoContent + + verifyAccount :: T.VerifyAccountRequest -> Handler NoContent + verifyAccount T.VerifyAccountRequest{..} = do + mPendingAccount <- liftIO $ PendingAccounts.get dbFile verifyAccountRequestUsername + case mPendingAccount of + Nothing -> + throwError err401 { errBody = "Either your secret or your username (or both) is invalid" } + Just pendingAccount@T.PendingAccount{..} -> + if pendingAccountSecret == verifyAccountRequestSecret then do + liftIO $ Accounts.transferFromPending dbFile pendingAccount + pure NoContent + else + throwError err401 { errBody = "The secret you provided is invalid" } + + deleteAccount :: T.SessionCookie -> Text -> Handler NoContent + deleteAccount cookie username = adminsOnly cookie $ do + liftIO $ Accounts.delete dbFile (T.Username username) + pure NoContent + + listAccounts :: T.SessionCookie -> Handler [T.User] + listAccounts cookie = adminsOnly cookie $ do + liftIO $ Accounts.list dbFile + + createTrip :: T.SessionCookie -> T.Trip -> Handler NoContent + createTrip cookie trip@T.Trip{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == tripUsername) $ do + liftIO $ Trips.create dbFile trip + pure NoContent + + updateTrip :: T.SessionCookie -> T.UpdateTripRequest -> Handler NoContent + updateTrip cookie updates@T.UpdateTripRequest{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == T.tripPKUsername updateTripRequestTripPK) $ do + mTrip <- liftIO $ Trips.get dbFile updateTripRequestTripPK + case mTrip of + Nothing -> throwError err400 { errBody = "tripKey is invalid" } + Just trip@T.Trip{..} -> do + -- TODO(wpcarro): Prefer function in Trips module that does this in a + -- DB transaction. + liftIO $ Trips.delete dbFile updateTripRequestTripPK + liftIO $ Trips.create dbFile (T.updateTrip updates trip) + pure NoContent + + deleteTrip :: T.SessionCookie -> T.TripPK -> Handler NoContent + deleteTrip cookie tripPK@T.TripPK{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == tripPKUsername) $ do + liftIO $ Trips.delete dbFile tripPK + pure NoContent + + listTrips :: T.SessionCookie -> Handler [T.Trip] + listTrips cookie = do + mAccount <- liftIO $ Auth.accountFromCookie dbFile cookie + case mAccount of + Nothing -> throwError err401 { errBody = "Your session cookie is invalid. Try logging out and logging back in." } + Just T.Account{..} -> + case accountRole of + T.Admin -> liftIO $ Trips.listAll dbFile + _ -> liftIO $ Trips.list dbFile accountUsername + + login :: T.AccountCredentials + -> Handler (Headers '[Header "Set-Cookie" SetCookie] T.Session) + login (T.AccountCredentials username password) = do + mAccount <- liftIO $ Accounts.lookup dbFile username + case mAccount of + Just account@T.Account{..} -> do + mAttempts <- liftIO $ LoginAttempts.forUsername dbFile accountUsername + case mAttempts of + Nothing -> + if T.passwordsMatch password accountPassword then do + uuid <- liftIO $ Sessions.findOrCreate dbFile account + pure $ addHeader (Auth.mkCookie uuid) + T.Session{ sessionUsername = accountUsername + , sessionRole = accountRole + } + else do + liftIO $ LoginAttempts.increment dbFile username + throwError err401 { errBody = "Your credentials are invalid" } + Just attempts -> + if attempts >= 3 then + throwError err429 + else if T.passwordsMatch password accountPassword then do + uuid <- liftIO $ Sessions.findOrCreate dbFile account + pure $ addHeader (Auth.mkCookie uuid) + T.Session{ sessionUsername = accountUsername + , sessionRole = accountRole + } + else do + liftIO $ LoginAttempts.increment dbFile username + throwError err401 { errBody = "Your credentials are invalid" } + + -- In this branch, the user didn't supply a known username. + Nothing -> throwError err401 { errBody = "Your credentials are invalid" } + + logout :: T.SessionCookie + -> Handler (Headers '[Header "Set-Cookie" SetCookie] NoContent) + logout cookie = do + case Auth.uuidFromCookie cookie of + Nothing -> + pure $ addHeader Auth.emptyCookie NoContent + Just uuid -> do + liftIO $ Sessions.delete dbFile uuid + pure $ addHeader Auth.emptyCookie NoContent + + unfreezeAccount :: T.SessionCookie + -> T.UnfreezeAccountRequest + -> Handler NoContent + unfreezeAccount cookie T.UnfreezeAccountRequest{..} = + adminsAnd cookie (\T.Account{..} -> accountRole == T.Manager) $ do + liftIO $ LoginAttempts.reset dbFile unfreezeAccountRequestUsername + pure NoContent + + inviteUser :: T.SessionCookie + -> T.InviteUserRequest + -> Handler NoContent + inviteUser cookie T.InviteUserRequest{..} = adminsOnly cookie $ do + secretUUID <- liftIO $ T.InvitationSecret <$> Random.randomIO + liftIO $ Invitations.create dbFile + secretUUID + inviteUserRequestEmail + inviteUserRequestRole + res <- liftIO $ sendInviteEmail config inviteUserRequestEmail secretUUID + case res of + Left _ -> undefined + Right _ -> pure NoContent + + acceptInvitation :: T.AcceptInvitationRequest -> Handler NoContent + acceptInvitation T.AcceptInvitationRequest{..} = do + mInvitation <- liftIO $ Invitations.get dbFile acceptInvitationRequestEmail + case mInvitation of + Nothing -> throwError err404 { errBody = "No invitation for email" } + Just T.Invitation{..} -> + if invitationSecret == acceptInvitationRequestSecret then do + liftIO $ Accounts.create dbFile + acceptInvitationRequestUsername + acceptInvitationRequestPassword + invitationEmail + invitationRole + pure NoContent + else + throwError err401 { errBody = "You are not providing a valid secret" } + +run :: T.Config -> IO () +run config@T.Config{..} = + Warp.run 3000 (enforceCors $ serve (Proxy @ API) $ server config) + where + enforceCors = Cors.cors (const $ Just corsPolicy) + corsPolicy :: Cors.CorsResourcePolicy + corsPolicy = + Cors.simpleCorsResourcePolicy + { Cors.corsOrigins = Just ([cs configClient], True) + , Cors.corsMethods = Cors.simpleMethods ++ ["PUT", "PATCH", "DELETE", "OPTIONS"] + , Cors.corsRequestHeaders = Cors.simpleHeaders ++ ["Content-Type", "Authorization"] + } diff --git a/users/wpcarro/assessments/tt/src/Auth.hs b/users/wpcarro/assessments/tt/src/Auth.hs new file mode 100644 index 000000000000..f1bff23257e0 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Auth.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Auth where +-------------------------------------------------------------------------------- +import Control.Monad.IO.Class (liftIO) +import Web.Cookie +import Servant + +import qualified Data.UUID as UUID +import qualified Sessions as Sessions +import qualified Accounts as Accounts +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Return the UUID from a Session cookie. +uuidFromCookie :: T.SessionCookie -> Maybe T.SessionUUID +uuidFromCookie (T.SessionCookie cookies) = do + auth <- lookup "auth" cookies + uuid <- UUID.fromASCIIBytes auth + pure $ T.SessionUUID uuid + +-- | Attempt to return the account associated with `cookie`. +accountFromCookie :: FilePath -> T.SessionCookie -> IO (Maybe T.Account) +accountFromCookie dbFile cookie = + case uuidFromCookie cookie of + Nothing -> pure Nothing + Just uuid -> do + mSession <- Sessions.get dbFile uuid + case mSession of + Nothing -> pure Nothing + Just T.StoredSession{..} -> do + mAccount <- Accounts.lookup dbFile storedSessionUsername + case mAccount of + Nothing -> pure Nothing + Just x -> pure (Just x) + +-- | Create a new session cookie. +mkCookie :: T.SessionUUID -> SetCookie +mkCookie (T.SessionUUID uuid) = + defaultSetCookie + { setCookieName = "auth" + , setCookieValue = UUID.toASCIIBytes uuid + } + +-- | Use this to clear out the session cookie. +emptyCookie :: SetCookie +emptyCookie = + defaultSetCookie + { setCookieName = "auth" + , setCookieValue = "" + } + +-- | Throw a 401 error if the `predicate` fails. +assert :: FilePath -> T.SessionCookie -> (T.Account -> Bool) -> Handler a -> Handler a +assert dbFile cookie predicate handler = do + mRole <- liftIO $ accountFromCookie dbFile cookie + case mRole of + Nothing -> throwError err401 { errBody = "Missing valid session cookie" } + Just account -> + if predicate account then + handler + else + throwError err401 { errBody = "You are not authorized to access this resource" } diff --git a/users/wpcarro/assessments/tt/src/Email.hs b/users/wpcarro/assessments/tt/src/Email.hs new file mode 100644 index 000000000000..2dac0973ba6d --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Email.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Email where +-------------------------------------------------------------------------------- +import Data.Text +import Data.String.Conversions (cs) +import Utils + +import qualified Mail.Hailgun as MG +import qualified Types as T +-------------------------------------------------------------------------------- + +newtype SendSuccess = SendSuccess MG.HailgunSendResponse + +data SendError + = MessageError MG.HailgunErrorMessage + | ResponseError MG.HailgunErrorResponse + +-- | Attempt to send an email with `subject` and with message, `body`. +send :: Text + -> Text + -> Text + -> T.Email + -> IO (Either SendError SendSuccess) +send apiKey subject body (T.Email to) = do + case mkMsg of + Left e -> pure $ Left (MessageError e) + Right x -> do + res <- MG.sendEmail ctx x + case res of + Left e -> pure $ Left (ResponseError e) + Right y -> pure $ Right (SendSuccess y) + where + ctx = MG.HailgunContext { MG.hailgunDomain = "sandboxda5038873f924b50af2f82a0f05cffdf.mailgun.org" + , MG.hailgunApiKey = cs apiKey + , MG.hailgunProxy = Nothing + } + mkMsg = MG.hailgunMessage + subject + (body |> cs |> MG.TextOnly) + "mailgun@sandboxda5038873f924b50af2f82a0f05cffdf.mailgun.org" + (MG.MessageRecipients { MG.recipientsTo = [cs to] + , MG.recipientsCC = [] + , MG.recipientsBCC = [] + }) + [] diff --git a/users/wpcarro/assessments/tt/src/Invitations.hs b/users/wpcarro/assessments/tt/src/Invitations.hs new file mode 100644 index 000000000000..0c700470f3e2 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Invitations.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Invitations where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +create :: FilePath -> T.InvitationSecret -> T.Email -> T.Role -> IO () +create dbFile secret email role = withConnection dbFile $ \conn -> do + execute conn "INSERT INTO Invitations (email,role,secret) VALUES (?,?,?)" + (email, role, secret) + +get :: FilePath -> T.Email -> IO (Maybe T.Invitation) +get dbFile email = withConnection dbFile $ \conn -> do + res <- query conn "SELECT email,role,secret FROM Invitations WHERE email = ?" (Only email) + case res of + [x] -> pure (Just x) + _ -> pure Nothing diff --git a/users/wpcarro/assessments/tt/src/LoginAttempts.hs b/users/wpcarro/assessments/tt/src/LoginAttempts.hs new file mode 100644 index 000000000000..d78e12e3fd8a --- /dev/null +++ b/users/wpcarro/assessments/tt/src/LoginAttempts.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module LoginAttempts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +reset :: FilePath -> T.Username -> IO () +reset dbFile username = withConnection dbFile $ \conn -> + execute conn "UPDATE LoginAttempts SET numAttempts = 0 WHERE username = ?" + (Only username) + +-- | Attempt to return the number of failed login attempts for +-- `username`. Returns a Maybe in case `username` doesn't exist. +forUsername :: FilePath -> T.Username -> IO (Maybe Integer) +forUsername dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,numAttempts FROM LoginAttempts WHERE username = ?" + (Only username) + case res of + [T.LoginAttempt{..}] -> pure (Just loginAttemptNumAttempts) + _ -> pure Nothing + +-- | INSERT a failed login attempt for `username` or UPDATE an existing entry. +increment :: FilePath -> T.Username -> IO () +increment dbFile username = withConnection dbFile $ \conn -> + execute conn "INSERT INTO LoginAttempts (username,numAttempts) VALUES (?,?) ON CONFLICT (username) DO UPDATE SET numAttempts = numAttempts + 1" + (username, 1 :: Integer) diff --git a/users/wpcarro/assessments/tt/src/Main.hs b/users/wpcarro/assessments/tt/src/Main.hs new file mode 100644 index 000000000000..9df4232066bb --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Main.hs @@ -0,0 +1,13 @@ +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import qualified App +import qualified System.Envy as Envy +-------------------------------------------------------------------------------- + +main :: IO () +main = do + mEnv <- Envy.decodeEnv + case mEnv of + Left err -> putStrLn err + Right env -> App.run env diff --git a/users/wpcarro/assessments/tt/src/PendingAccounts.hs b/users/wpcarro/assessments/tt/src/PendingAccounts.hs new file mode 100644 index 000000000000..a555185fa717 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/PendingAccounts.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module PendingAccounts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +create :: FilePath + -> T.RegistrationSecret + -> T.Username + -> T.ClearTextPassword + -> T.Role + -> T.Email + -> IO () +create dbFile secret username password role email = withConnection dbFile $ \conn -> do + hashed <- T.hashPassword password + execute conn "INSERT INTO PendingAccounts (secret,username,password,role,email) VALUES (?,?,?,?,?)" + (secret, username, hashed, role, email) + +get :: FilePath -> T.Username -> IO (Maybe T.PendingAccount) +get dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT secret,username,password,role,email FROM PendingAccounts WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +delete :: FilePath -> T.Username -> IO () +delete dbFile username = withConnection dbFile $ \conn -> + execute conn "DELETE FROM PendingAccounts WHERE username = ?" (Only username) diff --git a/users/wpcarro/assessments/tt/src/Sessions.hs b/users/wpcarro/assessments/tt/src/Sessions.hs new file mode 100644 index 000000000000..713059a38383 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Sessions.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +-------------------------------------------------------------------------------- +module Sessions where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Data.Time.Clock as Clock +import qualified Types as T +import qualified System.Random as Random +-------------------------------------------------------------------------------- + +-- | Return True if `session` was created at most three hours ago. +isValid :: T.StoredSession -> IO Bool +isValid session = do + t1 <- Clock.getCurrentTime + let t0 = T.storedSessionTsCreated session in + pure $ Clock.diffUTCTime t1 t0 <= 3 * 60 * 60 + +-- | Lookup the session by UUID. +get :: FilePath -> T.SessionUUID -> IO (Maybe T.StoredSession) +get dbFile uuid = withConnection dbFile $ \conn -> do + res <- query conn "SELECT uuid,username,tsCreated FROM Sessions WHERE uuid = ?" (Only uuid) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Lookup the session stored under `username` in `dbFile`. +find :: FilePath -> T.Username -> IO (Maybe T.StoredSession) +find dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT uuid,username,tsCreated FROM Sessions WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Create a session under the `username` key in `dbFile`. +create :: FilePath -> T.Username -> IO T.SessionUUID +create dbFile username = withConnection dbFile $ \conn -> do + now <- Clock.getCurrentTime + uuid <- Random.randomIO + execute conn "INSERT INTO Sessions (uuid,username,tsCreated) VALUES (?,?,?)" + (T.SessionUUID uuid, username, now) + pure (T.SessionUUID uuid) + +-- | Reset the tsCreated field to the current time to ensure the token is valid. +refresh :: FilePath -> T.SessionUUID -> IO () +refresh dbFile uuid = withConnection dbFile $ \conn -> do + now <- Clock.getCurrentTime + execute conn "UPDATE Sessions SET tsCreated = ? WHERE uuid = ?" + (now, uuid) + pure () + +-- | Delete the session under `username` from `dbFile`. +delete :: FilePath -> T.SessionUUID -> IO () +delete dbFile uuid = withConnection dbFile $ \conn -> + execute conn "DELETE FROM Sessions WHERE uuid = ?" (Only uuid) + +-- | Find or create a session in the Sessions table. If a session exists, +-- refresh the token's validity. +findOrCreate :: FilePath -> T.Account -> IO T.SessionUUID +findOrCreate dbFile account = + let username = T.accountUsername account in do + mSession <- find dbFile username + case mSession of + Nothing -> create dbFile username + Just session -> + let uuid = T.storedSessionUUID session in do + refresh dbFile uuid + pure uuid + +-- | Return a list of all sessions in the Sessions table. +list :: FilePath -> IO [T.StoredSession] +list dbFile = withConnection dbFile $ \conn -> + query_ conn "SELECT uuid,username,tsCreated FROM Sessions" diff --git a/users/wpcarro/assessments/tt/src/Trips.hs b/users/wpcarro/assessments/tt/src/Trips.hs new file mode 100644 index 000000000000..f90740363c52 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Trips.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Trips where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple +import Utils + +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Create a new `trip` in `dbFile`. +create :: FilePath -> T.Trip -> IO () +create dbFile trip = withConnection dbFile $ \conn -> + execute conn "INSERT INTO Trips (username,destination,startDate,endDate,comment) VALUES (?,?,?,?,?)" + (trip |> T.tripFields) + +-- | Attempt to get the trip record from `dbFile` under `tripKey`. +get :: FilePath -> T.TripPK -> IO (Maybe T.Trip) +get dbFile tripKey = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,destination,startDate,endDate,comment FROM Trips WHERE username = ? AND destination = ? AND startDate = ? LIMIT 1" + (T.tripPKFields tripKey) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Delete a trip from `dbFile` using its `tripKey` Primary Key. +delete :: FilePath -> T.TripPK -> IO () +delete dbFile tripKey = + withConnection dbFile $ \conn -> do + execute conn "DELETE FROM Trips WHERE username = ? AND destination = ? and startDate = ?" + (T.tripPKFields tripKey) + +-- | Return a list of all of the trips in `dbFile`. +listAll :: FilePath -> IO [T.Trip] +listAll dbFile = withConnection dbFile $ \conn -> + query_ conn "SELECT username,destination,startDate,endDate,comment FROM Trips ORDER BY date(startDate) ASC" + +-- | Return a list of all of the trips in `dbFile`. +list :: FilePath -> T.Username -> IO [T.Trip] +list dbFile username = withConnection dbFile $ \conn -> + query conn "SELECT username,destination,startDate,endDate,comment FROM Trips WHERE username = ? ORDER BY date(startDate) ASC" + (Only username) diff --git a/users/wpcarro/assessments/tt/src/Types.hs b/users/wpcarro/assessments/tt/src/Types.hs new file mode 100644 index 000000000000..6b06a39694fc --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Types.hs @@ -0,0 +1,544 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} +-------------------------------------------------------------------------------- +module Types where +-------------------------------------------------------------------------------- +import Data.Aeson +import Utils +import Data.Text +import Data.Typeable +import Database.SQLite.Simple +import Database.SQLite.Simple.Ok +import Database.SQLite.Simple.FromField +import Database.SQLite.Simple.ToField +import GHC.Generics +import Web.Cookie +import Servant.API +import System.Envy (FromEnv, fromEnv, env) +import Crypto.Random.Types (MonadRandom) + +import qualified Data.Time.Calendar as Calendar +import qualified Crypto.KDF.BCrypt as BC +import qualified Data.Time.Clock as Clock +import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString as BS +import qualified Data.Text.Encoding as TE +import qualified Data.Maybe as M +import qualified Data.UUID as UUID +-------------------------------------------------------------------------------- + +-- | Top-level application configuration. +data Config = Config + { mailgunAPIKey :: Text + , dbFile :: FilePath + , configClient :: Text + , configServer :: Text + } deriving (Eq, Show) + +instance FromEnv Config where + fromEnv _ = do + mailgunAPIKey <- env "MAILGUN_API_KEY" + dbFile <- env "DB_FILE" + configClient <- env "CLIENT" + configServer <- env "SERVER" + pure Config {..} + +-- TODO(wpcarro): Properly handle NULL for columns like profilePicture. +forNewtype :: (Typeable b) => (Text -> b) -> FieldParser b +forNewtype wrapper y = + case fieldData y of + (SQLText x) -> Ok (wrapper x) + x -> returnError ConversionFailed y ("We expected SQLText, but we received: " ++ show x) + +newtype Username = Username Text + deriving (Eq, Show, Generic) + +instance ToJSON Username +instance FromJSON Username + +instance ToField Username where + toField (Username x) = SQLText x + +instance FromField Username where + fromField = forNewtype Username + +newtype HashedPassword = HashedPassword BS.ByteString + deriving (Eq, Show, Generic) + +instance ToField HashedPassword where + toField (HashedPassword x) = SQLText (TE.decodeUtf8 x) + +instance FromField HashedPassword where + fromField y = + case fieldData y of + (SQLText x) -> x |> TE.encodeUtf8 |> HashedPassword |> Ok + x -> returnError ConversionFailed y ("We expected SQLText, but we received: " ++ show x) + +newtype ClearTextPassword = ClearTextPassword Text + deriving (Eq, Show, Generic) + +instance ToJSON ClearTextPassword +instance FromJSON ClearTextPassword + +instance ToField ClearTextPassword where + toField (ClearTextPassword x) = SQLText x + +instance FromField ClearTextPassword where + fromField = forNewtype ClearTextPassword + +newtype Email = Email Text + deriving (Eq, Show, Generic) + +instance ToJSON Email +instance FromJSON Email + +instance ToField Email where + toField (Email x) = SQLText x + +instance FromField Email where + fromField = forNewtype Email + +data Role = RegularUser | Manager | Admin + deriving (Eq, Show, Generic) + +instance ToJSON Role where + toJSON RegularUser = "user" + toJSON Manager = "manager" + toJSON Admin = "admin" + +instance FromJSON Role where + parseJSON = withText "Role" $ \x -> + case x of + "user" -> pure RegularUser + "manager" -> pure Manager + "admin" -> pure Admin + _ -> fail "Expected \"user\" or \"manager\" or \"admin\"" + +instance ToField Role where + toField RegularUser = SQLText "user" + toField Manager = SQLText "manager" + toField Admin = SQLText "admin" + +instance FromField Role where + fromField y = + case fieldData y of + (SQLText "user") -> Ok RegularUser + (SQLText "manager") -> Ok Manager + (SQLText "admin") -> Ok Admin + x -> returnError ConversionFailed y ("We expected user, manager, admin, but we received: " ++ show x) + +-- TODO(wpcarro): Prefer Data.ByteString instead of Text +newtype ProfilePicture = ProfilePicture Text + deriving (Eq, Show, Generic) + +instance ToJSON ProfilePicture +instance FromJSON ProfilePicture + +instance ToField ProfilePicture where + toField (ProfilePicture x) = SQLText x + +instance FromField ProfilePicture where + fromField = forNewtype ProfilePicture + +data Account = Account + { accountUsername :: Username + , accountPassword :: HashedPassword + , accountEmail :: Email + , accountRole :: Role + , accountProfilePicture :: Maybe ProfilePicture + } deriving (Eq, Show, Generic) + +-- | Return a tuple with all of the fields for an Account record to use for SQL. +accountFields :: Account -> (Username, HashedPassword, Email, Role, Maybe ProfilePicture) +accountFields (Account {..}) + = ( accountUsername + , accountPassword + , accountEmail + , accountRole + , accountProfilePicture + ) + +instance FromRow Account where + fromRow = do + accountUsername <- field + accountPassword <- field + accountEmail <- field + accountRole <- field + accountProfilePicture <- field + pure Account{..} + +data Session = Session + { sessionUsername :: Username + , sessionRole :: Role + } deriving (Eq, Show) + +instance ToJSON Session where + toJSON (Session username role) = + object [ "username" .= username + , "role" .= role + ] + +newtype Comment = Comment Text + deriving (Eq, Show, Generic) + +instance ToJSON Comment +instance FromJSON Comment + +instance ToField Comment where + toField (Comment x) = SQLText x + +instance FromField Comment where + fromField = forNewtype Comment + +newtype Destination = Destination Text + deriving (Eq, Show, Generic) + +instance ToJSON Destination +instance FromJSON Destination + +instance ToField Destination where + toField (Destination x) = SQLText x + +instance FromField Destination where + fromField = forNewtype Destination + +newtype Year = Year Integer deriving (Eq, Show) +newtype Month = Month Integer deriving (Eq, Show) +newtype Day = Day Integer deriving (Eq, Show) +data Date = Date + { dateYear :: Year + , dateMonth :: Month + , dateDay :: Day + } deriving (Eq, Show) + +data Trip = Trip + { tripUsername :: Username + , tripDestination :: Destination + , tripStartDate :: Calendar.Day + , tripEndDate :: Calendar.Day + , tripComment :: Comment + } deriving (Eq, Show, Generic) + +instance FromRow Trip where + fromRow = do + tripUsername <- field + tripDestination <- field + tripStartDate <- field + tripEndDate <- field + tripComment <- field + pure Trip{..} + +-- | The fields used as the Primary Key for a Trip entry. +data TripPK = TripPK + { tripPKUsername :: Username + , tripPKDestination :: Destination + , tripPKStartDate :: Calendar.Day + } deriving (Eq, Show, Generic) + +tripPKFields :: TripPK -> (Username, Destination, Calendar.Day) +tripPKFields (TripPK{..}) + = (tripPKUsername, tripPKDestination, tripPKStartDate) + +instance FromJSON TripPK where + parseJSON = withObject "TripPK" $ \x -> do + tripPKUsername <- x .: "username" + tripPKDestination <- x .: "destination" + tripPKStartDate <- x .: "startDate" + pure TripPK{..} + +-- | Return the tuple representation of a Trip record for SQL. +tripFields :: Trip + -> (Username, Destination, Calendar.Day, Calendar.Day, Comment) +tripFields (Trip{..}) + = ( tripUsername + , tripDestination + , tripStartDate + , tripEndDate + , tripComment + ) + +instance ToJSON Trip where + toJSON (Trip username destination startDate endDate comment) = + object [ "username" .= username + , "destination" .= destination + , "startDate" .= startDate + , "endDate" .= endDate + , "comment" .= comment + ] + +instance FromJSON Trip where + parseJSON = withObject "Trip" $ \x -> do + tripUsername <- x .: "username" + tripDestination <- x .: "destination" + tripStartDate <- x .: "startDate" + tripEndDate <- x .: "endDate" + tripComment <- x .: "comment" + pure Trip{..} + +-- | Users and Accounts both refer to the same underlying entities; however, +-- Users model the user-facing Account details, hiding sensitive details like +-- passwords and emails. +data User = User + { userUsername :: Username + , userProfilePicture :: Maybe ProfilePicture + , userRole :: Role + } deriving (Eq, Show, Generic) + +instance ToJSON User where + toJSON (User username profilePicture role) = + object [ "username" .= username + , "profilePicture" .= profilePicture + , "role" .= role + ] + +userFromAccount :: Account -> User +userFromAccount account = + User { userUsername = accountUsername account + , userProfilePicture = accountProfilePicture account + , userRole = accountRole account + } + +-- | This is the data that a user needs to supply to authenticate with the +-- application. +data AccountCredentials = AccountCredentials + { accountCredentialsUsername :: Username + , accountCredentialsPassword :: ClearTextPassword + } deriving (Eq, Show, Generic) + +instance FromJSON AccountCredentials where + parseJSON = withObject "AccountCredentials" $ \x -> do + accountCredentialsUsername <- x.: "username" + accountCredentialsPassword <- x.: "password" + pure AccountCredentials{..} + + +-- | Hash password `x`. +hashPassword :: (MonadRandom m) => ClearTextPassword -> m HashedPassword +hashPassword (ClearTextPassword x) = do + hashed <- BC.hashPassword 12 (x |> unpack |> B.pack) + pure $ HashedPassword hashed + +-- | Return True if the cleartext password matches the hashed password. +passwordsMatch :: ClearTextPassword -> HashedPassword -> Bool +passwordsMatch (ClearTextPassword clear) (HashedPassword hashed) = + BC.validatePassword (clear |> unpack |> B.pack) hashed + +data CreateAccountRequest = CreateAccountRequest + { createAccountRequestUsername :: Username + , createAccountRequestPassword :: ClearTextPassword + , createAccountRequestEmail :: Email + , createAccountRequestRole :: Role + } deriving (Eq, Show) + +instance FromJSON CreateAccountRequest where + parseJSON = withObject "CreateAccountRequest" $ \x -> do + createAccountRequestUsername <- x .: "username" + createAccountRequestPassword <- x .: "password" + createAccountRequestEmail <- x .: "email" + createAccountRequestRole <- x .: "role" + pure $ CreateAccountRequest{..} + +createAccountRequestFields :: CreateAccountRequest + -> (Username, ClearTextPassword, Email, Role) +createAccountRequestFields CreateAccountRequest{..} = + ( createAccountRequestUsername + , createAccountRequestPassword + , createAccountRequestEmail + , createAccountRequestRole + ) + +newtype SessionUUID = SessionUUID UUID.UUID + deriving (Eq, Show, Generic) + +instance FromField SessionUUID where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert to UUID: " ++ show x) + Just uuid -> Ok $ SessionUUID uuid + _ -> returnError ConversionFailed y "Expected SQLText for SessionUUID, but we received" + +instance ToField SessionUUID where + toField (SessionUUID uuid) = + uuid |> UUID.toText |> SQLText + +data StoredSession = StoredSession + { storedSessionUUID :: SessionUUID + , storedSessionUsername :: Username + , storedSessionTsCreated :: Clock.UTCTime + } deriving (Eq, Show, Generic) + +instance FromRow StoredSession where + fromRow = do + storedSessionUUID <- field + storedSessionUsername <- field + storedSessionTsCreated <- field + pure StoredSession {..} + +data LoginAttempt = LoginAttempt + { loginAttemptUsername :: Username + , loginAttemptNumAttempts :: Integer + } deriving (Eq, Show) + +instance FromRow LoginAttempt where + fromRow = do + loginAttemptUsername <- field + loginAttemptNumAttempts <- field + pure LoginAttempt {..} + +newtype SessionCookie = SessionCookie Cookies + +instance FromHttpApiData SessionCookie where + parseHeader x = + x |> parseCookies |> SessionCookie |> pure + parseQueryParam x = + x |> TE.encodeUtf8 |> parseCookies |> SessionCookie |> pure + +newtype RegistrationSecret = RegistrationSecret UUID.UUID + deriving (Eq, Show, Generic) + +instance FromHttpApiData RegistrationSecret where + parseQueryParam x = + case UUID.fromText x of + Nothing -> Left x + Just uuid -> Right (RegistrationSecret uuid) + +instance FromField RegistrationSecret where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert text to UUID: " ++ show x) + Just uuid -> Ok $ RegistrationSecret uuid + _ -> returnError ConversionFailed y "Field data is not SQLText, which is what we expect" + +instance ToField RegistrationSecret where + toField (RegistrationSecret secretUUID) = + secretUUID |> UUID.toText |> SQLText + +instance FromJSON RegistrationSecret + +data VerifyAccountRequest = VerifyAccountRequest + { verifyAccountRequestUsername :: Username + , verifyAccountRequestSecret :: RegistrationSecret + } deriving (Eq, Show) + +instance FromJSON VerifyAccountRequest where + parseJSON = withObject "VerifyAccountRequest" $ \x -> do + verifyAccountRequestUsername <- x .: "username" + verifyAccountRequestSecret <- x .: "secret" + pure VerifyAccountRequest{..} + +data PendingAccount = PendingAccount + { pendingAccountSecret :: RegistrationSecret + , pendingAccountUsername :: Username + , pendingAccountPassword :: HashedPassword + , pendingAccountRole :: Role + , pendingAccountEmail :: Email + } deriving (Eq, Show) + +instance FromRow PendingAccount where + fromRow = do + pendingAccountSecret <- field + pendingAccountUsername <- field + pendingAccountPassword <- field + pendingAccountRole <- field + pendingAccountEmail <- field + pure PendingAccount {..} + +data UpdateTripRequest = UpdateTripRequest + { updateTripRequestTripPK :: TripPK + , updateTripRequestDestination :: Maybe Destination + , updateTripRequestStartDate :: Maybe Calendar.Day + , updateTripRequestEndDate :: Maybe Calendar.Day + , updateTripRequestComment :: Maybe Comment + } deriving (Eq, Show) + +instance FromJSON UpdateTripRequest where + parseJSON = withObject "UpdateTripRequest" $ \x -> do + updateTripRequestTripPK <- x .: "tripKey" + -- the following four fields might not be present + updateTripRequestDestination <- x .:? "destination" + updateTripRequestStartDate <- x .:? "startDate" + updateTripRequestEndDate <- x .:? "endDate" + updateTripRequestComment <- x .:? "comment" + pure UpdateTripRequest{..} + +-- | Apply the updates in the UpdateTripRequest to Trip. +updateTrip :: UpdateTripRequest -> Trip -> Trip +updateTrip UpdateTripRequest{..} Trip{..} = Trip + { tripUsername = tripUsername + , tripDestination = M.fromMaybe tripDestination updateTripRequestDestination + , tripStartDate = M.fromMaybe tripStartDate updateTripRequestStartDate + , tripEndDate = M.fromMaybe tripEndDate updateTripRequestEndDate + , tripComment = M.fromMaybe tripComment updateTripRequestComment + } + +data UnfreezeAccountRequest = UnfreezeAccountRequest + { unfreezeAccountRequestUsername :: Username + } deriving (Eq, Show) + +instance FromJSON UnfreezeAccountRequest where + parseJSON = withObject "UnfreezeAccountRequest" $ \x -> do + unfreezeAccountRequestUsername <- x .: "username" + pure UnfreezeAccountRequest{..} + +data InviteUserRequest = InviteUserRequest + { inviteUserRequestEmail :: Email + , inviteUserRequestRole :: Role + } deriving (Eq, Show) + +instance FromJSON InviteUserRequest where + parseJSON = withObject "InviteUserRequest" $ \x -> do + inviteUserRequestEmail <- x .: "email" + inviteUserRequestRole <- x .: "role" + pure InviteUserRequest{..} + +newtype InvitationSecret = InvitationSecret UUID.UUID + deriving (Eq, Show, Generic) + +instance ToJSON InvitationSecret +instance FromJSON InvitationSecret + +instance ToField InvitationSecret where + toField (InvitationSecret secretUUID) = + secretUUID |> UUID.toText |> SQLText + +instance FromField InvitationSecret where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert text to UUID: " ++ show x) + Just z -> Ok $ InvitationSecret z + _ -> returnError ConversionFailed y "Field data is not SQLText, which is what we expect" + +data Invitation = Invitation + { invitationEmail :: Email + , invitationRole :: Role + , invitationSecret :: InvitationSecret + } deriving (Eq, Show) + +instance FromRow Invitation where + fromRow = Invitation <$> field + <*> field + <*> field + +data AcceptInvitationRequest = AcceptInvitationRequest + { acceptInvitationRequestUsername :: Username + , acceptInvitationRequestPassword :: ClearTextPassword + , acceptInvitationRequestEmail :: Email + , acceptInvitationRequestSecret :: InvitationSecret + } deriving (Eq, Show) + +instance FromJSON AcceptInvitationRequest where + parseJSON = withObject "AcceptInvitationRequest" $ \x -> do + acceptInvitationRequestUsername <- x .: "username" + acceptInvitationRequestPassword <- x .: "password" + acceptInvitationRequestEmail <- x .: "email" + acceptInvitationRequestSecret <- x .: "secret" + pure AcceptInvitationRequest{..} diff --git a/users/wpcarro/assessments/tt/src/Utils.hs b/users/wpcarro/assessments/tt/src/Utils.hs new file mode 100644 index 000000000000..48c33af0796d --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Utils.hs @@ -0,0 +1,9 @@ +-------------------------------------------------------------------------------- +module Utils where +-------------------------------------------------------------------------------- +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +-- | Prefer this operator to the ampersand for stylistic reasons. +(|>) :: a -> (a -> b) -> b +(|>) = (&) diff --git a/users/wpcarro/assessments/tt/src/init.sql b/users/wpcarro/assessments/tt/src/init.sql new file mode 100644 index 000000000000..b42753ae5d01 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/init.sql @@ -0,0 +1,67 @@ +-- Run `.read init.sql` from within a SQLite3 REPL to initialize the tables we +-- need for this application. This will erase all current entries, so use with +-- caution. +-- Make sure to set `PRAGMA foreign_keys = on;` when transacting with the +-- database. + +BEGIN TRANSACTION; + +DROP TABLE IF EXISTS Accounts; +DROP TABLE IF EXISTS Trips; +DROP TABLE IF EXISTS Sessions; +DROP TABLE IF EXISTS LoginAttempts; +DROP TABLE IF EXISTS PendingAccounts; +DROP TABLE IF EXISTS Invitations; + +CREATE TABLE Accounts ( + username TEXT CHECK(LENGTH(username) > 0) NOT NULL, + password TEXT CHECK(LENGTH(password) > 0) NOT NULL, + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + profilePicture BLOB, + PRIMARY KEY (username) +); + +CREATE TABLE Trips ( + username TEXT NOT NULL, + destination TEXT CHECK(LENGTH(destination) > 0) NOT NULL, + startDate TEXT CHECK(LENGTH(startDate) == 10) NOT NULL, -- 'YYYY-MM-DD' + endDate TEXT CHECK(LENGTH(endDate) == 10) NOT NULL, -- 'YYYY-MM-DD' + comment TEXT NOT NULL, + PRIMARY KEY (username, destination, startDate), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE Sessions ( + uuid TEXT CHECK(LENGTH(uuid) == 36) NOT NULL, + username TEXT NOT NULL UNIQUE, + -- TODO(wpcarro): Add a LENGTH CHECK here + tsCreated TEXT NOT NULL, -- 'YYYY-MM-DD HH:MM:SS' + PRIMARY KEY (uuid), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE LoginAttempts ( + username TEXT NOT NULL UNIQUE, + numAttempts INTEGER NOT NULL, + PRIMARY KEY (username), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE PendingAccounts ( + secret TEXT CHECK(LENGTH(secret) == 36) NOT NULL, + username TEXT CHECK(LENGTH(username) > 0) NOT NULL, + password TEXT CHECK(LENGTH(password) > 0) NOT NULL, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + PRIMARY KEY (username) +); + +CREATE TABLE Invitations ( + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + secret TEXT CHECK(LENGTH(secret) == 36) NOT NULL, + PRIMARY KEY (email) +); + +COMMIT; diff --git a/users/wpcarro/assessments/tt/tests/create-accounts.sh b/users/wpcarro/assessments/tt/tests/create-accounts.sh new file mode 100755 index 000000000000..8c2a66bc8bd7 --- /dev/null +++ b/users/wpcarro/assessments/tt/tests/create-accounts.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# This script populates the Accounts table over HTTP. + +http POST :3000/accounts \ + username=mimi \ + password=testing \ + email=miriamwright@google.com \ + role=user + +http POST :3000/accounts \ + username=bill \ + password=testing \ + email=wpcarro@gmail.com \ + role=manager + +http POST :3000/accounts \ + username=wpcarro \ + password=testing \ + email=wpcarro@google.com \ + role=admin diff --git a/users/wpcarro/assessments/tt/todo.org b/users/wpcarro/assessments/tt/todo.org new file mode 100644 index 000000000000..39592d04826b --- /dev/null +++ b/users/wpcarro/assessments/tt/todo.org @@ -0,0 +1,18 @@ +* TODO Users must be able to create an account +* TODO Users must verify their account by email +* TODO Support federated login with Google +* TODO Users must be able to authenticate and login +* TODO Define three roles: user, manager, admin +* TODO Users can add trips +* TODO Users can edit trips +* TODO Users can delete trips +* TODO Users can filter trips +* TODO Support all actions via the REST API +* TODO Block users after three failed authentication attempts +* TODO Only admins and managers can unblock blocked login attempts +* TODO Add unit tests +* TODO Add E2E tests +* TODO Pull user profile pictures using Gravatar +* TODO Allow users to change their profile picture +* TODO Admins should be allowed to invite new users via email +* TODO Allow users to print their travel itineraries diff --git a/users/wpcarro/boilerplate/README.md b/users/wpcarro/boilerplate/README.md new file mode 100644 index 000000000000..0ff4afa5b24d --- /dev/null +++ b/users/wpcarro/boilerplate/README.md @@ -0,0 +1,21 @@ +# Boilerplate + +Storing some boilerplate code to help me reduce the time it takes me to develop +and deploy applications. + +## Usage + +Let's say that you would like to create a game for +`sandbox.wpcarro.dev/game`. We will create a new TypeScript project with the +following: + +```shell +$ cp ~/briefcase/boilerplate/typescript ~/briefcase/website/sandbox/game +``` + +This initializes the project. To start developing, run: + +```shell +$ nix-shell +$ yarn run dev +``` diff --git a/users/wpcarro/boilerplate/clojure/.envrc b/users/wpcarro/boilerplate/clojure/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/clojure/.gitignore b/users/wpcarro/boilerplate/clojure/.gitignore new file mode 100644 index 000000000000..f24c5e393a28 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/.gitignore @@ -0,0 +1,4 @@ +/.lein-repl-history +/target +/? +/.nrepl-port \ No newline at end of file diff --git a/users/wpcarro/boilerplate/clojure/README.md b/users/wpcarro/boilerplate/clojure/README.md new file mode 100644 index 000000000000..2eec7f75e063 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/README.md @@ -0,0 +1,33 @@ +# Clojure Boilerplate + +This boilerplate uses `lein` to manage the project. + +## Files to change + +To use this boilerplate, run the following in a shell: + +```shell +$ cp ~/briefcase/boilerplate/clojure path/to/new-project +``` + +After running the above command, change the following files to remove the +placeholder values: + +- `README.md`: Change the title; change the description; drop "Files to change"; + keep "Getting started" +- `project.clj`: Change title +- `src/main.clj`: Change `:doc`; drop `main/foo` + +## Getting started + +From a shell, run: + +```shell +$ lein repl +``` + +From Emacs, navigate to a source code buffer and run: + +``` +M-x cider-jack-in +``` diff --git a/users/wpcarro/boilerplate/clojure/project.clj b/users/wpcarro/boilerplate/clojure/project.clj new file mode 100644 index 000000000000..54e34eab7a5d --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/project.clj @@ -0,0 +1,2 @@ +(defproject boilerplate "0.0.1" + :dependencies [[org.clojure/clojure "1.8.0"]]) diff --git a/users/wpcarro/boilerplate/clojure/shell.nix b/users/wpcarro/boilerplate/clojure/shell.nix new file mode 100644 index 000000000000..efa854422eae --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + leiningen + ]; +} diff --git a/users/wpcarro/boilerplate/clojure/src/main.clj b/users/wpcarro/boilerplate/clojure/src/main.clj new file mode 100644 index 000000000000..f6b60dba404e --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/src/main.clj @@ -0,0 +1,8 @@ +(ns ^{:doc "Top-level module." + :author "William Carroll"} + main) + +(declare main) + +(defn foo [a b] + (+ a b)) diff --git a/users/wpcarro/boilerplate/elm/.envrc b/users/wpcarro/boilerplate/elm/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/elm/.gitignore b/users/wpcarro/boilerplate/elm/.gitignore new file mode 100644 index 000000000000..1cb4f3034cc3 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css diff --git a/users/wpcarro/boilerplate/elm/README.md b/users/wpcarro/boilerplate/elm/README.md new file mode 100644 index 000000000000..04804ad94fac --- /dev/null +++ b/users/wpcarro/boilerplate/elm/README.md @@ -0,0 +1,18 @@ +# Elm + +Elm has one of the best developer experiences that I'm aware of. The error +messages are helpful and the entire experience is optimized to improve the ease +of writing web applications. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ npx tailwindcss build index.css -o output.css +$ elm-live -- src/Main.elm --output=Main.min.js +``` + +You can now view your web client at `http://localhost:8000`! diff --git a/users/wpcarro/boilerplate/elm/elm.json b/users/wpcarro/boilerplate/elm/elm.json new file mode 100644 index 000000000000..a95f80408ec4 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/elm.json @@ -0,0 +1,30 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/boilerplate/elm/index.css b/users/wpcarro/boilerplate/elm/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/boilerplate/elm/index.html b/users/wpcarro/boilerplate/elm/index.html new file mode 100644 index 000000000000..ce8f727b6f3b --- /dev/null +++ b/users/wpcarro/boilerplate/elm/index.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Elm SPA</title> + <link rel="stylesheet" href="./output.css" /> + <script src="./Main.min.js"></script> + </head> + <body class="font-serif"> + <div id="mount"></div> + <script> + Elm.Main.init({node: document.getElementById("mount")}); + </script> + </body> +</html> diff --git a/users/wpcarro/boilerplate/elm/shell.nix b/users/wpcarro/boilerplate/elm/shell.nix new file mode 100644 index 000000000000..00bb4b0b3edc --- /dev/null +++ b/users/wpcarro/boilerplate/elm/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs.elmPackages; [ + elm + elm-format + elm-live + ]; +} diff --git a/users/wpcarro/boilerplate/elm/src/Landing.elm b/users/wpcarro/boilerplate/elm/src/Landing.elm new file mode 100644 index 000000000000..00bb9e281af4 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Landing.elm @@ -0,0 +1,13 @@ +module Landing exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State + + +render : State.Model -> Html State.Msg +render model = + div [ class "pt-10 pb-20 px-10" ] + [ p [] [ text "Welcome to the landing page!" ] + ] diff --git a/users/wpcarro/boilerplate/elm/src/Login.elm b/users/wpcarro/boilerplate/elm/src/Login.elm new file mode 100644 index 000000000000..27f1d811a89a --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Login.elm @@ -0,0 +1,13 @@ +module Login exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State + + +render : State.Model -> Html State.Msg +render model = + div [ class "pt-10 pb-20 px-10" ] + [ p [] [ text "Please authenticate" ] + ] diff --git a/users/wpcarro/boilerplate/elm/src/Main.elm b/users/wpcarro/boilerplate/elm/src/Main.elm new file mode 100644 index 000000000000..30006460cde9 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Main.elm @@ -0,0 +1,31 @@ +module Main exposing (main) + +import Browser +import Html exposing (..) +import Landing +import Login +import State + + +subscriptions : State.Model -> Sub State.Msg +subscriptions model = + Sub.none + + +view : State.Model -> Html State.Msg +view model = + case model.view of + State.Landing -> + Landing.render model + + State.Login -> + Login.render model + + +main = + Browser.element + { init = \() -> ( State.init, Cmd.none ) + , subscriptions = subscriptions + , update = State.update + , view = view + } diff --git a/users/wpcarro/boilerplate/elm/src/State.elm b/users/wpcarro/boilerplate/elm/src/State.elm new file mode 100644 index 000000000000..c1edae8bb638 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/State.elm @@ -0,0 +1,43 @@ +module State exposing (..) + + +type Msg + = DoNothing + | SetView View + + +type View + = Landing + | Login + + +type alias Model = + { isLoading : Bool + , view : View + } + + +{-| The initial state for the application. +-} +init : Model +init = + { isLoading = False + , view = Landing + } + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + DoNothing -> + ( model, Cmd.none ) + + SetView x -> + ( { model + | view = x + , isLoading = True + } + , Cmd.none + ) diff --git a/users/wpcarro/boilerplate/typescript/.envrc b/users/wpcarro/boilerplate/typescript/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/typescript/.gitignore b/users/wpcarro/boilerplate/typescript/.gitignore new file mode 100644 index 000000000000..ebea22e071dd --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/.gitignore @@ -0,0 +1,3 @@ +/.cache +/dist +/node_modules \ No newline at end of file diff --git a/users/wpcarro/boilerplate/typescript/README.md b/users/wpcarro/boilerplate/typescript/README.md new file mode 100644 index 000000000000..a54186a9f29e --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/README.md @@ -0,0 +1,26 @@ +# Frontend Boilerplate + +While many times I prefer using alt-languages like ReasonML, ClojureScript, or +Elm, sometimes I prefer to write an application using TypeScript. This directory +contains the necessary starter code to create these applications. + +- React: Maps application state to UI +- React-Router: Stateful routing for SPAs +- Redux: Application state management +- TypeScript: Type-safety +- TailwindCSS: Styling library using utility classes +- Prettier: Source code formatting +- Jest: Test runner + +## Developing + +```shell +$ nix-shell +$ yarn run dev +``` + +## Building + +```shell +$ nix-build +``` diff --git a/users/wpcarro/boilerplate/typescript/default.nix b/users/wpcarro/boilerplate/typescript/default.nix new file mode 100644 index 000000000000..14a39dee1785 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/default.nix @@ -0,0 +1,19 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "typescript"; + srcs = builtins.path { path = ./.; name = "typescript"; }; + buildInputs = with pkgs; [ + nodejs + # Exposes lscpu for parcel.js + utillinux + ]; + # parcel.js needs number of CPUs + PARCEL_WORKERS = "1"; + buildPhase = '' + npx parcel build src/index.html --public-url ./ + ''; + installPhase = '' + mv dist $out + ''; +} diff --git a/users/wpcarro/boilerplate/typescript/package.json b/users/wpcarro/boilerplate/typescript/package.json new file mode 100644 index 000000000000..104e7272da93 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/package.json @@ -0,0 +1,27 @@ +{ + "name": "tailwindcss", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "parcel src/index.html & npx tsc --watch --noEmit", + "prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\"" + }, + "devDependencies": { + "@types/node": "^13.9.3", + "parcel-bundler": "^1.12.4", + "prettier": "^2.0.2", + "tailwindcss": "^1.2.0", + "typescript": "^3.8.3" + }, + "dependencies": { + "@reduxjs/toolkit": "^1.2.5", + "@types/react-dom": "^16.9.5", + "@types/react-redux": "^7.1.7", + "@types/react-router-dom": "^5.1.3", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.1.2" + } +} diff --git a/users/wpcarro/boilerplate/typescript/postcss.config.js b/users/wpcarro/boilerplate/typescript/postcss.config.js new file mode 100644 index 000000000000..d68fa618664e --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/postcss.config.js @@ -0,0 +1,7 @@ +const tailwindcss = require('tailwindcss') + +module.exports = { + plugins: [ + tailwindcss('./tailwind.config.js') + ] +} diff --git a/users/wpcarro/boilerplate/typescript/shell.nix b/users/wpcarro/boilerplate/typescript/shell.nix new file mode 100644 index 000000000000..083254beefd0 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/shell.nix @@ -0,0 +1,9 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + yarn + ]; +} diff --git a/users/wpcarro/boilerplate/typescript/src/App.tsx b/users/wpcarro/boilerplate/typescript/src/App.tsx new file mode 100644 index 000000000000..4fae1b36ac4d --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/App.tsx @@ -0,0 +1,52 @@ +import React, { useEffect } from "react"; +import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import { actions, useTypedSelector } from "./store"; +import { Link } from "react-router-dom"; + +const App: React.FC = () => { + const dispatch = useDispatch(); + const { isLoading } = useTypedSelector(state => ({ + isLoading: state.isLoading, + })); + + return ( + <Router> + <nav className="bg-blue-400"> + <ul className="container mx-auto justify-between flex py-6 text-white"> + <li> + <Link to="/">Home</Link> + </li> + <li> + <Link to="/about">About</Link> + </li> + <li> + <Link to="/contact">Contact</Link> + </li> + </ul> + </nav> + <Switch> + <Route exact path="/"> + <div className="container mx-auto"> + <h1>Welcome to the home page. Loading: {isLoading ? "true" : "false"}</h1> + <button + className="bg-gray-300 py-4 px-6" + onClick={() => dispatch(actions.toggleIsLoading())}>isLoading</button> + </div> + </Route> + <Route exact path="/about"> + <div className="container mx-auto"> + <h1>Here is the about page.</h1> + </div> + </Route> + <Route exact path="/contact"> + <div className="container mx-auto"> + <h1>Here is the contact page.</h1> + </div> + </Route> + </Switch> + </Router> + ); +}; + +export default App; diff --git a/users/wpcarro/boilerplate/typescript/src/index.css b/users/wpcarro/boilerplate/typescript/src/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/boilerplate/typescript/src/index.html b/users/wpcarro/boilerplate/typescript/src/index.html new file mode 100644 index 000000000000..91752af916a4 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="./index.css" /> + </head> + <body> + <div id="mount"></div> + <script src="./index.tsx"></script> + </body> +</html> diff --git a/users/wpcarro/boilerplate/typescript/src/index.tsx b/users/wpcarro/boilerplate/typescript/src/index.tsx new file mode 100644 index 000000000000..dc28dc4a9cc8 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import { Provider } from "react-redux"; +import store from "./store"; + +ReactDOM.render( + <Provider store={store}> + <App /> + </Provider>, + document.getElementById("mount") +); diff --git a/users/wpcarro/boilerplate/typescript/src/store.ts b/users/wpcarro/boilerplate/typescript/src/store.ts new file mode 100644 index 000000000000..03e980a491cc --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/store.ts @@ -0,0 +1,26 @@ +import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit"; +import { useSelector, TypedUseSelectorHook } from "react-redux"; + +export interface State { + isLoading: boolean; +} + +const initialState: State = { + isLoading: true, +}; + +export const { actions, reducer } = createSlice({ + name: "application", + initialState, + reducers: { + toggleIsLoading: state => ({ ...state, isLoading: !state.isLoading }), + } +}); + +/** + * Defining and consuming this allows us to avoid annotating State in all of our + * selectors. + */ +export const useTypedSelector: TypedUseSelectorHook<State> = useSelector; + +export default configureStore({ reducer }); diff --git a/users/wpcarro/boilerplate/typescript/tailwind.config.js b/users/wpcarro/boilerplate/typescript/tailwind.config.js new file mode 100644 index 000000000000..af829e20f9cb --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + theme: { + extend: {}, + }, + variants: {}, + plugins: [], +} diff --git a/users/wpcarro/boilerplate/typescript/tsconfig.json b/users/wpcarro/boilerplate/typescript/tsconfig.json new file mode 100644 index 000000000000..013f34fdf0b1 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src/**/*" + ] +} diff --git a/users/wpcarro/boilerplate/typescript/yarn.lock b/users/wpcarro/boilerplate/typescript/yarn.lock new file mode 100644 index 000000000000..0e16fe80a47c --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/yarn.lock @@ -0,0 +1,5670 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== + dependencies: + browserslist "^4.9.1" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.4", "@babel/generator@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" + integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== + dependencies: + "@babel/types" "^7.9.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-builder-react-jsx-experimental@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" + integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-module-imports" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-builder-react-jsx@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" + integrity sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-compilation-targets@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" + integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== + dependencies: + "@babel/compat-data" "^7.8.6" + browserslist "^4.9.1" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== + dependencies: + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" + integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.4.4", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.3.tgz#043a5fc2ad8b7ea9facddc4e802a1f0f25da7255" + integrity sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A== + +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" + integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.8" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-flow@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz#f2c883bd61a6316f2c89380ae5122f923ba4527f" + integrity sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" + integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" + integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" + integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-split-export-declaration" "^7.8.3" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" + integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-flow-strip-types@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" + integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-flow" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-modules-amd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== + dependencies: + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.8.7": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" + integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.1.tgz#d03af29396a6dc51bfa24eefd8005a9fd381152a" + integrity sha512-+xIZ6fPoix7h57CNO/ZeYADchg1tFyX9NDsnmNFFua8e1JNPln156mzS+8AQe1On2X2GLlANHJWHIXbMCqWDkQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.9.0" + "@babel/helper-builder-react-jsx-experimental" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" + integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" + integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.0" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.0" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.4.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" + integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" + integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@iarna/toml@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" + integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@parcel/fs@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-1.11.0.tgz#fb8a2be038c454ad46a50dc0554c1805f13535cd" + integrity sha512-86RyEqULbbVoeo8OLcv+LQ1Vq2PKBAvWTU9fCgALxuCTbbs5Ppcvll4Vr+Ko1AnmMzja/k++SzNAwJfeQXVlpA== + dependencies: + "@parcel/utils" "^1.11.0" + mkdirp "^0.5.1" + rimraf "^2.6.2" + +"@parcel/logger@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" + integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== + dependencies: + "@parcel/workers" "^1.11.0" + chalk "^2.1.0" + grapheme-breaker "^0.3.2" + ora "^2.1.0" + strip-ansi "^4.0.0" + +"@parcel/utils@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" + integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== + +"@parcel/watcher@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" + integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== + dependencies: + "@parcel/utils" "^1.11.0" + chokidar "^2.1.5" + +"@parcel/workers@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-1.11.0.tgz#7b8dcf992806f4ad2b6cecf629839c41c2336c59" + integrity sha512-USSjRAAQYsZFlv43FUPdD+jEGML5/8oLF0rUzPQTtK4q9kvaXr49F5ZplyLz5lox78cLZ0TxN2bIDQ1xhOkulQ== + dependencies: + "@parcel/utils" "^1.11.0" + physical-cpu-count "^2.0.0" + +"@reduxjs/toolkit@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.2.5.tgz#149aa62da12a18a67a30495cb63fd897003f2272" + integrity sha512-/OWoW5mniUXAomw4+3ZhhWodcs1/SRvK2HKyxLXdW6vKgmJhiBiSHe/huHARlKWujEmGaJrkafx548GE494bCQ== + dependencies: + immer "^4.0.1" + redux "^4.0.0" + redux-devtools-extension "^2.13.8" + redux-immutable-state-invariant "^2.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@^13.9.3": + version "13.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" + integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" + integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + +"@types/react-dom@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7" + integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a" + integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" + integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*": + version "16.9.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e" + integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn-walk@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + +acorn@^6.0.1, acorn@^6.0.4: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +ansi-to-html@^0.6.4: + version "0.6.14" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" + integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== + dependencies: + entities "^1.1.2" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.4.5: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + dependencies: + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.26" + postcss-value-parser "^4.0.2" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-runtime@^6.11.6, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-types@^6.15.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon-walk@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babylon-walk/-/babylon-walk-1.0.2.tgz#3b15a5ddbb482a78b4ce9c01c8ba181702d9d6ce" + integrity sha1-OxWl3btIKni0zpwByLoYFwLZ1s4= + dependencies: + babel-runtime "^6.11.6" + babel-types "^6.15.0" + lodash.clone "^4.5.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brfs@^1.2.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" + integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== + dependencies: + quote-stream "^1.0.1" + resolve "^1.1.5" + static-module "^2.2.0" + through2 "^2.0.0" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.8.3, browserslist@^4.9.1: + version "4.11.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.0.tgz#aef4357b10a8abda00f97aac7cd587b2082ba1ad" + integrity sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.380" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035: + version "1.0.30001036" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" + integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" + integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" + integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== + +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@~1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + dependencies: + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^2.4.0, core-js@^2.6.5: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.4: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-modules-loader-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" + integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= + dependencies: + icss-replace-symbols "1.1.0" + postcss "6.0.1" + postcss-modules-extract-imports "1.1.0" + postcss-modules-local-by-default "1.2.0" + postcss-modules-scope "1.1.0" + postcss-modules-values "1.3.0" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-selector-tokenizer@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" + integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + regexpu-core "^4.6.0" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= + +css-what@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" + integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.0.0, cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" + integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + dependencies: + css-tree "1.0.0-alpha.37" + +cssom@0.3.x, cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +csstype@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +deasync@^0.1.14: + version "0.1.19" + resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.19.tgz#e7ea89fcc9ad483367e8a48fe78f508ca86286e8" + integrity sha512-oh3MRktfnPlLysCPpBpKZZzb4cUC/p0aA3SyRGp15lN30juJBTo/CiD0d4fR+f1kBtUQoJj1NE9RPNWQ7BQ9Mg== + dependencies: + bindings "^1.5.0" + node-addon-api "^1.7.1" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.380: + version "1.3.381" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.381.tgz#952678ff91a5f36175a3832358a6dd2de3bf62b7" + integrity sha512-JQBpVUr83l+QOqPQpj2SbOve1bBE4ACpmwcMNqWlZmfib7jccxJ02qFNichDpZ5LS4Zsqc985NIPKegBIZjK8Q== + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +entities@^1.1.1, entities@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +envinfo@^7.3.1: + version "7.5.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" + integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.11.0, escodegen@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +falafel@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" + integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + dependencies: + acorn "^7.1.1" + foreach "^2.0.5" + isarray "^2.0.1" + object-keys "^1.0.6" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-glob@^2.2.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +filesize@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +grapheme-breaker@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz#5b9e6b78c3832452d2ba2bb1cb830f96276410ac" + integrity sha1-W55reMODJFLSuiuxy4MPlidkEKw= + dependencies: + brfs "^1.2.0" + unicode-trie "^0.3.1" + +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-tags@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" + integrity sha1-x43mW1Zjqll5id0rerSSANfk25g= + +htmlnano@^0.2.2: + version "0.2.5" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-0.2.5.tgz#134fd9548c7cbe51c8508ce434a3f9488cff1b0b" + integrity sha512-X1iPSwXG/iF9bVs+/obt2n6F64uH0ETkA8zp7qFDmLW9/+A6ueHGeb/+qD67T21qUY22owZPMdawljN50ajkqA== + dependencies: + cssnano "^4.1.10" + normalize-html-whitespace "^1.0.0" + posthtml "^0.12.0" + posthtml-render "^1.1.5" + purgecss "^1.4.0" + svgo "^1.3.2" + terser "^4.3.9" + uncss "^0.17.2" + +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +immer@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-4.0.2.tgz#9ff0fcdf88e06f92618a5978ceecb5884e633559" + integrity sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-html@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-html/-/is-html-1.1.0.tgz#e04f1c18d39485111396f9a0273eab51af218464" + integrity sha1-4E8cGNOUhRETlvmgJz6rUa8hhGQ= + dependencies: + html-tags "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isarray@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" + integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== + dependencies: + vlq "^0.2.2" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + dependencies: + source-map "^0.5.6" + +merge2@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-libs-browser@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" + +normalize-html-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" + integrity sha512-9ui7CGtOOlehQu0t/OhhlmDyc71mKVlv+4vF+me4iZLPrNtRL2xoquEdfZxasC/bdQi/Hr3iTrpyRKIG+ocabA== + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-inspect@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" + integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-2.1.0.tgz#6caf2830eb924941861ec53a173799e008b51e5b" + integrity sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA== + dependencies: + chalk "^2.3.1" + cli-cursor "^2.1.0" + cli-spinners "^1.1.0" + log-symbols "^2.2.0" + strip-ansi "^4.0.0" + wcwidth "^1.0.1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parcel-bundler@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" + integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.4.4" + "@babel/generator" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/plugin-transform-flow-strip-types" "^7.4.4" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/preset-env" "^7.4.4" + "@babel/runtime" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + "@iarna/toml" "^2.2.0" + "@parcel/fs" "^1.11.0" + "@parcel/logger" "^1.11.1" + "@parcel/utils" "^1.11.0" + "@parcel/watcher" "^1.12.1" + "@parcel/workers" "^1.11.0" + ansi-to-html "^0.6.4" + babylon-walk "^1.0.2" + browserslist "^4.1.0" + chalk "^2.1.0" + clone "^2.1.1" + command-exists "^1.2.6" + commander "^2.11.0" + core-js "^2.6.5" + cross-spawn "^6.0.4" + css-modules-loader-core "^1.1.0" + cssnano "^4.0.0" + deasync "^0.1.14" + dotenv "^5.0.0" + dotenv-expand "^5.1.0" + envinfo "^7.3.1" + fast-glob "^2.2.2" + filesize "^3.6.0" + get-port "^3.2.0" + htmlnano "^0.2.2" + is-glob "^4.0.0" + is-url "^1.2.2" + js-yaml "^3.10.0" + json5 "^1.0.1" + micromatch "^3.0.4" + mkdirp "^0.5.1" + node-forge "^0.7.1" + node-libs-browser "^2.0.0" + opn "^5.1.0" + postcss "^7.0.11" + postcss-value-parser "^3.3.1" + posthtml "^0.11.2" + posthtml-parser "^0.4.0" + posthtml-render "^1.1.3" + resolve "^1.4.0" + semver "^5.4.1" + serialize-to-js "^3.0.0" + serve-static "^1.12.4" + source-map "0.6.1" + terser "^3.7.3" + v8-compile-cache "^2.0.0" + ws "^5.1.1" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +physical-cpu-count@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" + integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-calc@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" + integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-nested@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248" + integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw== + dependencies: + postcss "^7.0.21" + postcss-selector-parser "^6.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-selector-parser@6.0.2, postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1, postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27: + version "7.0.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" + integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +posthtml-parser@^0.4.0, posthtml-parser@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" + integrity sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg== + dependencies: + htmlparser2 "^3.9.2" + +posthtml-render@^1.1.3, posthtml-render@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.2.0.tgz#3df0c800a8bbb95af583a94748520469477addf4" + integrity sha512-dQB+hoAKDtnI94RZm/wxBUH9My8OJcXd0uhWmGh2c7tVtQ85A+OS3yCN3LNbFtPz3bViwBJXAeoi+CBGMXM0DA== + +posthtml@^0.11.2: + version "0.11.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" + integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +posthtml@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.12.0.tgz#6e2a2fcd774eaed1a419a95c5cc3a92b676a40a6" + integrity sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" + integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +purgecss@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.4.2.tgz#67ab50cb4f5c163fcefde56002467c974e577f41" + integrity sha512-hkOreFTgiyMHMmC2BxzdIw5DuC6kxAbP/gGOGd3MEsF3+5m69rIvUEPaxrnoUtfODTFKe9hcXjGwC6jcjoyhOw== + dependencies: + glob "^7.1.3" + postcss "^7.0.14" + postcss-selector-parser "^6.0.0" + yargs "^14.0.0" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +quote-stream@^1.0.1, quote-stream@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" + integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + dependencies: + buffer-equal "0.0.1" + minimist "^1.1.3" + through2 "^2.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-dom@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-redux@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +redux-devtools-extension@^2.13.8: + version "2.13.8" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" + integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== + +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpu-core@^4.6.0, regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.5, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.4.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-to-js@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" + integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== + +serve-static@^1.12.4: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-copy@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +static-eval@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.5.tgz#f0782e66999c4b3651cda99d9ce59c507d188f71" + integrity sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA== + dependencies: + escodegen "^1.11.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +static-module@^2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" + integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== + dependencies: + concat-stream "~1.6.0" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "~1.9.0" + falafel "^2.1.0" + has "^1.0.1" + magic-string "^0.22.4" + merge-source-map "1.0.4" + object-inspect "~1.4.0" + quote-stream "~1.0.2" + readable-stream "~2.3.3" + shallow-copy "~0.0.1" + static-eval "^2.0.0" + through2 "~2.0.3" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0, svgo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291" + integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ== + dependencies: + autoprefixer "^9.4.5" + bytes "^3.0.0" + chalk "^3.0.0" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +terser@^3.7.3: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + +terser@^4.3.9: + version "4.6.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" + integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0, through2@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +uncss@^0.17.2: + version "0.17.3" + resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.3.tgz#50fc1eb4ed573ffff763458d801cd86e4d69ea11" + integrity sha512-ksdDWl81YWvF/X14fOSw4iu8tESDHFIeyKIeDrK6GEVTQvqJc1WlOEXqostNwOCi3qAj++4EaLsdAgPmUbEyog== + dependencies: + commander "^2.20.0" + glob "^7.1.4" + is-absolute-url "^3.0.1" + is-html "^1.1.0" + jsdom "^14.1.0" + lodash "^4.17.15" + postcss "^7.0.17" + postcss-selector-parser "6.0.2" + request "^2.88.0" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unicode-trie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" + integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^5.1.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3" + integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^14.0.0: + version "14.2.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" + integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== + dependencies: + cliui "^5.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^15.0.1" diff --git a/users/wpcarro/buildHaskell/default.nix b/users/wpcarro/buildHaskell/default.nix new file mode 100644 index 000000000000..5958b1ea26ae --- /dev/null +++ b/users/wpcarro/buildHaskell/default.nix @@ -0,0 +1,31 @@ +{ pkgs, ... }: + +{ + # Create a nix-shell for Haskell development. + shell = { deps }: let + ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: deps hpkgs); + in pkgs.mkShell { + buildInputs = [ghc]; + }; + + # Build a Haskell executable. This assumes a project directory with a + # top-level Main.hs. + # - `name`: You can find the result at ./result/$name + # - `srcs`: Will be passed to `srcs` field of `pkgs.stdenv.mkDerivation`. + # - `deps`: A function that accepts `hpkgs` and returns a list of Haskell + # - `ghcExtensions`: A list of strings representing the language extensions to + # use. + program = { name, srcs, deps, ghcExtensions }: let + ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: deps hpkgs); + in pkgs.stdenv.mkDerivation { + name = name; + buildInputs = []; + srcs = srcs; + buildPhase = '' + ${ghc}/bin/ghc -Wall Main.hs ${pkgs.lib.concatMapStrings (x: "-X${x} ") ghcExtensions} + ''; + installPhase = '' + mkdir -p $out && mv Main $out/${name} + ''; + }; +} diff --git a/users/wpcarro/ci/pipelines/post-receive.nix b/users/wpcarro/ci/pipelines/post-receive.nix new file mode 100644 index 000000000000..456d546af7da --- /dev/null +++ b/users/wpcarro/ci/pipelines/post-receive.nix @@ -0,0 +1,56 @@ +{ briefcase, pkgs, ... }: + +let + inherit (builtins) fetchGit path toJSON; + inherit (briefcase.emacs) initEl runScript; + + elispLintSrc = fetchGit { + url = "https://github.com/gonewest818/elisp-lint"; + rev = "2b645266be8010a6a49c6d0ebf6a3ad5bd290ff4"; + }; + + pipeline.steps = [ + { + key = "lint-secrets"; + command = "${pkgs.git-secrets}/bin/git-secrets --scan-history"; + label = ":broom: lint secrets"; + } + { + key = "build-briefcase"; + command = '' + nix-build . -I briefcase="$(pwd)" --no-out-link --show-trace + ''; + label = ":nix: build briefcase"; + depends_on = "lint-secrets"; + } + { + key = "init-emacs"; + command = let + scriptEl = path { + path = ./script.el; + name = "script.el"; + }; + runScriptEl = runScript { + script = scriptEl; + briefcasePath = "$(pwd)"; + }; + in "${runScriptEl} ${initEl}"; + label = ":gnu: initialize Emacs"; + depends_on = "build-briefcase"; + } + { + key = "build-socrates"; + command = '' + nix-build '<nixpkgs/nixos>' \ + -I briefcase="$(pwd)" \ + -I nixpkgs=/var/lib/buildkite-agent-socrates/nixpkgs-channels \ + -I nixos-config=nixos/socrates/default.nix \ + -A system \ + --no-out-link \ + --show-trace + ''; + label = ":nix: build socrates"; + depends_on = "build-briefcase"; + } + ]; +in pkgs.writeText "pipeline.yaml" (toJSON pipeline) diff --git a/users/wpcarro/ci/pipelines/script.el b/users/wpcarro/ci/pipelines/script.el new file mode 100644 index 000000000000..da079b64ba5b --- /dev/null +++ b/users/wpcarro/ci/pipelines/script.el @@ -0,0 +1,44 @@ +;; This script initializes Emacs and exits with either a zero or non-zero status +;; depending on whether or not Emacs initialized without logging warnings or +;; encountering errors. +;; +;; This script reads the location of init.el as the last argument in `argv'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'dash) +(require 'buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Script +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar init-el-path (-last-item argv) + "Path to the init.el file that this script attempts to load.") + +(prelude-assert (f-exists? init-el-path)) + +(condition-case err + (load init-el-path) + (error + (message "Encountered an error while attempting to load init.el: %s" err) + (kill-emacs 1))) + +(when (buffer-exists? "*Errors*") + (progn + (with-current-buffer "*Errors*" + (message "Encountered errors in *Errors* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(when (buffer-exists? "*Warnings*") + (progn + (with-current-buffer "*Warnings*" + (message "Encountered warnings in *Warnings* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(message "Successfully init'd Emacs without encountering errors or warnings!") +(kill-emacs 0) diff --git a/users/wpcarro/ci/secret-patterns.txt b/users/wpcarro/ci/secret-patterns.txt new file mode 100644 index 000000000000..cbf58a1e744b --- /dev/null +++ b/users/wpcarro/ci/secret-patterns.txt @@ -0,0 +1,9 @@ +(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16} +("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')? +("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')? +AIza[0-9A-Za-z_-]{35} +[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com +(^|[^0-9A-Za-z/+])1/[0-9A-Za-z_-]{43} +(^|[^0-9A-Za-z/+])1/[0-9A-Za-z_-]{64} +ya29\.[0-9A-Za-z_-]+ +(sk|pk)_(test|live)_[a-zA-Z0-9]{99} diff --git a/users/wpcarro/configs/.config/fish/prompt.fish b/users/wpcarro/configs/.config/fish/prompt.fish new file mode 100644 index 000000000000..58d22dab5efa --- /dev/null +++ b/users/wpcarro/configs/.config/fish/prompt.fish @@ -0,0 +1,87 @@ +# When the Emacs SSH client, Tramp, connects to a remote host that uses Fish, +# it's important to keep the shell prompt simple so that Tramp can parse it. +if test "$TERM" = "dumb" + function fish_prompt + echo "\$ " + end + function fish_right_prompt; end + function fish_greeting; end + function fish_title; end +else + function fish_prompt + # My custom prompt. + # + # Design objectives: + # - max-length <= 80 characters + # - minimal + # - no dependencies (well, you know what I mean) + # + # Components + # - ssh connection + # - user + # - host + # - git repo + # - git branch + # - lambda character as prompt + + # Cache status before we overwrite it. + set -l last_status $status + + # Colors + set -l color_inactive (set_color red --bold) + set -l color_active (set_color green --bold) + set -l color_normal (set_color normal) + + # SSH information + if set -q SSH_CLIENT; or set -q SSH_TTY + echo -en "$color_active \bssh โ [$color_normal$USER@"(hostname)"$color_active]$color_normal" + else + echo -en "$color_inactive \bssh โ [$color_normal$USER@"(hostname)"$color_inactive]$color_normal" + end + + # Separator + echo -n " " + + # Git information + set -l git_repo (git rev-parse --show-toplevel 2>/dev/null) + set -l git_status $status + + if [ (realpath .) = "/" ] + set -g dir_path (realpath .) + else if [ (realpath ..) = "/" ] + set -g dir_path (realpath .) + else + set -g dir_path (echo (basename (realpath ..))"/"(basename (realpath .))) + end + + if test $git_status -eq 0 + set -l git_repo_name (basename (git rev-parse --show-toplevel)) + set -l git_branch (git branch 2>/dev/null | grep '^\*' | cut -d' ' -f2-) + echo -en "$color_active \bgit โ [$color_normal$git_branch$color_active|$color_normal$git_repo_name$color_active|$color_normal$dir_path$color_active]$color_normal" + else + echo -en "$color_inactive \bgit โ [$color_normal$dir_path$color_inactive]$color_normal" + end + + # Newline + echo + + # Handle root vs non-root + if [ "$USER" = "root" ] + set -g prompt_sigil "#" + else + set -g prompt_sigil "ฮป" + end + + set -l time (date +"%T") + if test $last_status -eq 0 + set -l color_prompt (set_color white --bold) + echo -n "$time$color_prompt $prompt_sigil$color_normal " + else + set -l color_prompt (set_color red --bold) + echo -n "$time$color_prompt $prompt_sigil$color_normal " + end + end + function fish_right_prompt; end + function fish_greeting; end + function fish_title; end +end diff --git a/users/wpcarro/configs/.config/nixpkgs/config.nix b/users/wpcarro/configs/.config/nixpkgs/config.nix new file mode 100644 index 000000000000..1dd1750ae025 --- /dev/null +++ b/users/wpcarro/configs/.config/nixpkgs/config.nix @@ -0,0 +1,3 @@ +{ + allowUnfree = true; +} diff --git a/users/wpcarro/configs/.config/nvim/init.vim b/users/wpcarro/configs/.config/nvim/init.vim new file mode 100644 index 000000000000..57cfe7ea6a20 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/init.vim @@ -0,0 +1,668 @@ +" -- BEGIN: Vundle config -- +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +" share Vundle between vim and neovim +set rtp+=~/.vim/bundle/Vundle.vim +set rtp+=~/.config/nvim/bundle/Vundle.vim +call vundle#begin() +" alternatively, pass a path where Vundle should install plugins +"call vundle#begin('~/some/path/here') + +" let Vundle manage Vundle, required +Plugin 'VundleVim/Vundle.vim' + +" Rust IDE features +Plugin 'racer-rust/vim-racer' + +set hidden +let g:racer_experimental_completer = 1 +autocmd FileType rust nmap gd <Plug>(rust-def) +autocmd FileType rust nmap gs <Plug>(rust-def-split) +autocmd FileType rust nmap gx <Plug>(rust-def-vertical) +autocmd FileType rust nmap <leader>gd <Plug>(rust-doc) + +Plugin 'xolox/vim-misc' + +" The following are examples of different formats supported. +" Keep Plugin commands between vundle#begin/end. + +" Displays git information in airline. +Plugin 'tpope/vim-fugitive' + +" easier file navigation +Plugin 'tpope/vim-vinegar' + +" Displays git-tracked C*UD ops within gutter. +Plugin 'airblade/vim-gitgutter' + +" Fuzzy-finder +Plugin 'kien/ctrlp.vim' + +" Grep file contents +Plugin 'mileszs/ack.vim' + +" Syntax and other light-weight suppor for a variety of languages +Plugin 'sheerun/vim-polyglot' + +" Themes +Plugin 'deviantfero/wpgtk.vim' +Plugin 'rainglow/vim' + + +" Executes shell commands and pipes output into new Vim buffer. +Plugin 'sjl/clam.vim' + +" Multiple cursors for simultaneous edits. +" NOTE: use <C-n> to run miltiple cursors not <C-d> +Plugin 'terryma/vim-multiple-cursors' + +" Visualize buffers +Plugin 'vim-airline/vim-airline' +Plugin 'vim-airline/vim-airline-themes' + +" Visually align assignments +Plugin 'godlygeek/tabular' + +" Visually Highlight and comment code. +Plugin 'tpope/vim-commentary' + +" Macros for quotes, parens, etc. +Plugin 'tpope/vim-surround' + +" Allows Plugins to be repeated with `.` character +Plugin 'tpope/vim-repeat' + +" Pairs of mappings +Plugin 'tpope/vim-unimpaired' + +" LISPs support +Plugin 'guns/vim-sexp' +Plugin 'tpope/vim-sexp-mappings-for-regular-people' +let g:sexp_enable_insert_mode_mappings = 0 +let g:sexp_filetypes = '' + +" Seamlessly navigate Vim and Tmux with similar bindings. +Plugin 'christoomey/vim-tmux-navigator' + +" Async `:make` for code linting etc. +Plugin 'neomake/neomake' + +" Better buffer mgt than CtrlP +Plugin 'yegappan/mru' + +Plugin 'zanglg/nova.vim' + +" Emulates Emacs's Helm Swoop search +Plugin 'pelodelfuego/vim-swoop' + +" Transparent encryption + decryption +Plugin 'jamessan/vim-gnupg' + +" Javascript auto-formatting +" Plugin 'prettier/vim-prettier', { +" \ 'do': 'yarn install', + " \ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql', 'markdown'] } + +" Support Org mode +Plugin 'jceb/vim-orgmode' + +" Autocompletion +Plugin 'junegunn/fzf' + +" Text objects made easy +Plugin 'kana/vim-textobj-user' + +" Elixir text objects +Plugin 'andyl/vim-textobj-elixir' + +" Making HTML editing faster +Plugin 'mattn/emmet-vim' + +" Snippets for all languages +Plugin 'honza/vim-snippets' + +" Automatic bracket insertion +Plugin 'jiangmiao/auto-pairs' + +" Linting & error warnings +Plugin 'vim-syntastic/syntastic' + +" Angular.js support +Plugin 'burnettk/vim-angular' + +" Asynchronous Linting Engine +Plugin 'w0rp/ale' + +call vundle#end() " required +filetype plugin indent on " required +" Put your non-Plugin stuff after this line +" -- END: Vundle config -- + +" Changes <leader> to <space> character. +let mapleader = " " + + +" Highlight column width +set textwidth=80 +set colorcolumn=+0 + +" autoreload a file when it changes on disk +set autoread + +" default to case-insensitive searching +set ignorecase + +" JSX configuration +let g:jsx_ext_required = 0 + + +autocmd FileType reason nnoremap <buffer> gd :call LanguageClient_textDocument_definition()<CR> +autocmd FileType reason nnoremap <buffer> gf :call LanguageClient_textDocument_formatting()<CR> +autocmd FileType reason nnoremap <buffer> gh :call LanguageClient_textDocument_hover()<CR> +autocmd FileType reason nnoremap <buffer> gr :call LanguageClient_textDocument_rename()<CR> + +" Replace <CR> with G for faster navigation +nnoremap <CR> G +onoremap <CR> G +vnoremap <CR> G + +" Mirror ZLE KBD +inoremap <M-'> :echo "Working"<CR> + +" Syntastic configuration +set statusline+=%#warningmsg# +set statusline+=%{SyntasticStatuslineFlag()} +set statusline+=%* + +let g:syntastic_always_populate_loc_list = 1 +let g:syntastic_auto_loc_list = 1 +let g:syntastic_check_on_open = 1 +let g:syntastic_check_on_wq = 0 +" let g:syntastic_javascript_checkers = ['eslint'] +let g:syntastic_javascript_eslint_generic = 1 +" this is a hack to prevent a false negative +" https://github.com/vim-syntastic/syntastic/issues/1692 +" let g:syntastic_javascript_eslint_exec = '/bin/ls' +" let g:syntastic_javascript_eslint_exe = 'npx eslint' +" let g:syntastic_javascript_eslint_args = '-f compact' + +" javascript autocompletion +" autocmd FileType javascript setlocal omnifunc=javascriptcomplete#CompleteJS +" autocmd FileType javascript nnoremap <buffer> gf :Prettier<CR> + +" Maximize the current window +" Similar to Tmux mapping alt-z in my tmux.conf +nnoremap t% :tab sp<CR> + +" Allow C-g to act like C-c the way it does in Emacs +cnoremap <C-g> <C-c> + +" Prettier configuration +" let g:prettier#exec_cmd_async = 1 +" force Prettier to run on files even without the @format pragma +" let g:prettier#autoformat = 0 + + +" Basic settings +" Thin cursor on INSERT mode +if has('nvim') + let $NVIM_TUI_ENABLE_CURSOR_SHAPE = 1 +endif + +set number +set nowrap +set tabstop=2 +set expandtab +set shiftwidth=2 +set background=dark + +syntax enable +colorscheme peacock + +" Vim in terminal cannot have a different font from the one set within your +" terminal. However, this setting will set the font for the GUI version. +if has('gui_running') + set guifont=Operator\ Mono:h12 +endif + +if has('termguicolors') + set termguicolors +endif + +if &term =~# '^screen' + let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum" + let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum" +endif + +set history=1000 +set undolevels=1000 + +set t_Co=255 + +" Support italics +highlight Comment cterm=italic + + +" quickly edit popular configuration files +nnoremap <leader>ev :vsplit $MYVIMRC<CR> +nnoremap <leader>ee :vsplit ~/.emacs.d/init.el<CR> +nnoremap <leader>ez :vsplit ~/.zshrc<CR> +nnoremap <leader>ea :vsplit ~/aliases.zsh<CR> +nnoremap <leader>ef :vsplit ~/functions.zsh<CR> +nnoremap <leader>el :vsplit ~/variables.zsh<CR> +nnoremap <leader>ex :vsplit ~/.Xresources<CR> + +" quickly source your vimrc +nnoremap <leader>sv :source $MYVIMRC<CR> + +" quickly edit your snippets +nnoremap <leader>es :vsplit<CR>:edit ~/.vim/bundle/vim-snippets/snippets/reason.snippets<CR> + + +" Auto resize window splits +autocmd VimResized * wincmd = + + +" Neomake Settings +autocmd! BufWritePost * Neomake + +" Elixir linting +let g:neomake_elixir_credo_maker = { + \ 'exe': 'mix', + \ 'args': ['credo', 'list', '%:p', '--format=oneline'], + \ 'errorformat': + \ '%W[F] %. %f:%l:%c %m,' . + \ '%W[F] %. %f:%l %m,' . + \ '%W[R] %. %f:%l:%c %m,' . + \ '%W[R] %. %f:%l %m,' . + \ '%I[C] %. %f:%l:%c %m,' . + \ '%I[C] %. %f:%l %m,' . + \ '%-Z%.%#' + \ } + + +let g:neomake_elixir_enabled_makers = ['mix', 'credo'] + +augroup my_error_signs + au! + autocmd ColorScheme * hi NeomakeErrorSign ctermfg=203 guifg=#ff5f5f + autocmd ColorScheme * hi NeomakeWarningSign ctermfg=209 guifg=#ffaf00 + autocmd ColorScheme * hi NeomakeInfoSign ctermfg=183 guifg=#dfafff + autocmd ColorScheme * hi NeomakeMessageSign ctermfg=27 guifg=#0087ff +augroup END + + +" templates +if has("autocmd") + autocmd BufNewFile *.c 0r ~/.config/nvim/templates/boilerplate.c + autocmd BufNewFile *.rs 0r ~/.config/nvim/templates/boilerplate.rs +endif + +let g:neomake_error_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeErrorSign', + \ } + +let g:neomake_warning_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeWarningSign', + \ } + +let g:neomake_info_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeInfoSign', + \ } + +let g:neomake_message_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeMessageSign', + \ } + +function! <SID>LocationPrevious() + try + lprev + catch /^Vim\%((\a\+)\)\=:E553/ + llast + endtry +endfunction + +function! <SID>LocationNext() + try + lnext + catch /^Vim\%((\a\+)\)\=:E553/ + lfirst + endtry +endfunction + +nnoremap <Leader>[ :call <SID>LocationPrevious()<CR> +nnoremap <Leader>] :call <SID>LocationNext()<CR> + + +" Alchemist settings +let g:alchemist#elixir_erlang_src = '/usr/local/share/src' + + +" Airline Settings +" Enables the list of buffers. +let g:airline#extensions#tabline#enabled = 0 + +" Buffer numbers alongside files +let g:airline#extensions#tabline#buffer_nr_show = 0 + +" Shows the filename only. +let g:airline#extensions#tabline#fnamemod = ':t' + +" Allow glyphs in airline +let g:airline_powerline_fonts = 1 + +" Change Airline theme +let g:airline_theme = 'hybrid' + + +" Vim-Swoop Settings +" Edits colorscheme +let g:swoopHighlight = ["hi! link SwoopBufferLineHi Warning", "hi! link SwoopPatternHi Error"] + + +" Jump to buffers. +nmap <F1> :1b<CR> +nmap <F2> :2b<CR> +nmap <F3> :3b<CR> +nmap <F4> :4b<CR> +nmap <F5> :5b<CR> +nmap <F6> :6b<CR> +nmap <F7> :7b<CR> +nmap <F8> :8b<CR> +nmap <F9> :9b<CR> + + +" It's the twenty-first century...no swaps. +set noswapfile + + +" Allow visual tab completion in command mode +set wildmenu + + +" Show Vim commands as they're being input. +set showcmd + + +" Code folding +" set foldmethod=indent +" set foldnestmax=10 +" set nofoldenable +" set foldlevel=4 + + +" emulate ci" and ci' behavior +nnoremap ci( f(%ci( +nnoremap ci[ f[%ci[ + + +" extend functionality of <C-e> & <C-y> scrolling +nnoremap <C-e> <C-e>j +vnoremap <C-e> <C-e>j +nnoremap <C-y> <C-y>k +vnoremap <C-y> <C-y>k + + +" Opens all folds within the buffer +" nnoremap ZZ zR + +" Closes all folds within the buffer +" nnoremap zz zM + +" Opens all folds beneath the cursor +" NOTE: j is the character to go down +" nnoremap zJ zO + +" Opens single fold beneath the cursor +" NOTE: j is the character to go down +" nnoremap zj zo + +" Opens single fold beneath the cursor +" NOTE: k is the character to go down +" nnoremap zK zC + +" Opens single fold beneath the cursor +" NOTE: k is the character to go down +" nnoremap zk zc + + +" Save shortcut +nnoremap <C-s> :w<CR> + + +" Switch to MRU'd buffer +nnoremap <leader><leader> <C-^> + + +" Alternative MRU to CtrlP MRU +nnoremap <leader>b :MRU<CR> + + +" Supports mouse interaction. +set mouse=a + + +" Highlights matches during a search. +set hlsearch + +" Clear highlight +noremap <silent> <leader>h :nohlsearch<bar>:echo<CR> + + +" backspace settings +set backspace=2 +set backspace=indent,eol,start + + +" Javascript specific variables +let g:javascript_plugin_jsdoc = 1 + +" GlobalListchars +set list +set listchars=tab:ยทยท,trail:ยท,nbsp:ยท + + +" Keeps everything concealed at all times. Even when cursor is on the word. +set conceallevel=1 +set concealcursor=nvic + + +" map jk to <Esc> +inoremap jk <Esc> + + +" Hybrid mode for Vim +inoremap <C-a> <Esc>I +inoremap <C-e> <Esc>A + +inoremap <M-b> <S-Left> +inoremap <M-f> <S-Right> + +inoremap <C-b> <Left> +inoremap <C-f> <Right> +inoremap <C-p> <Up> +inoremap <C-n> <Down> + +" temporarily disable <C-p> in normal mode so it doesn't attempt to index all of +" Google3. +nnoremap <C-p> :echo "You are attempting to index all of Google3. Aborting..."<CR> + +" tab maintenence +nnoremap <C-t> :tabnew<CR> +nnoremap <C-w> :tabclose<CR> +nnoremap <Tab> :tabnext<CR> +nnoremap <S-Tab> :tabprevious<CR> + +" Manage Vertical and Horizontal splits +nnoremap sl <Esc>:vs<CR><C-w>l +nnoremap sh <Esc>:vs<CR> +nnoremap sj <Esc>:sp<CR><C-w>j +nnoremap sk <Esc>:sp<CR> + + +" Delete (i.e. "close") the currently opened buffer +" TODO: unless it's a split window, which should be :q +nnoremap <leader>q :bdelete<CR> + + +" Set CtrlP runtime path +set runtimepath^=~/.vim/bundle/ctrlp.vim + + +" Pane movement +let g:tmux_navigator_no_mappings = 1 + +nnoremap <silent> <M-h> :TmuxNavigateLeft<CR> +nnoremap <silent> <M-j> :TmuxNavigateDown<CR> +nnoremap <silent> <M-k> :TmuxNavigateUp<CR> +nnoremap <silent> <M-l> :TmuxNavigateRight<CR> +nnoremap <silent> <M-q> :q<CR> + +" make Y do what is intuitive given: +" D: deletes until EOL +" C: changes until EOL +" Y: (should) yank until EOL +nnoremap Y y$ + + +" scrolling and maintaing mouse position +" nnoremap <C-j> j<C-e> +" nnoremap <C-k> k<C-y> + + +" remap redo key that is eclipsed by `rotate` currently +nnoremap U :redo<CR> + + +" Define highlighting groups +" NOTE: The ANSII aliases for colors will change when iTerm2 settings are +" changed. +highlight InterestingWord1 ctermbg=Magenta ctermfg=Black +highlight InterestingWord2 ctermbg=Blue ctermfg=Black + +" h1 highlighting +nnoremap <silent> <leader>1 :execute '2match InterestingWord1 /\<<c-r><c-w>\>/'<CR> +nnoremap <silent> <leader>x1 :execute '2match none'<CR> +vnoremap <silent> <leader>1 :execute '2match InterestingWord1 /\<<c-r><c-w>\>/'<CR> + +" h2 highlighting +nnoremap <silent> <leader>2 :execute '3match InterestingWord2 /\<<c-r><c-w>\>/'<CR> +nnoremap <silent> <leader>x2 :execute '3match none'<CR> + +"clear all highlighted groups +nnoremap <silent> <leader>xx :execute '2match none'<CR> :execute '3match none'<CR> hh + + +" pasteboard copy & paste +set clipboard+=unnamedplus + + +" Manage 80 char line limits +highlight OverLength1 ctermbg=Magenta ctermfg=Black +highlight OverLength2 ctermbg=LightMagenta ctermfg=Black +highlight OverLength3 ctermbg=White ctermfg=Black +" match OverLength3 /\%81v.\+/ +match OverLength2 /\%91v.\+/ +" match OverLength3 /\%101v.\+/ + +nnoremap <leader>w :w<CR> + + +" Resize split to 10,20,...,100 chars +" Uncomment the next lines for support at those sizes. +" These bindings interfere with the highlight groups, however. +" Increases the width of a vertical split. +" nnoremap <leader>1 :vertical resize 10<CR> +" nnoremap <leader>2 :vertical resize 20<CR> +nnoremap <leader>3 :vertical resize 30<CR> +nnoremap <leader>4 :vertical resize 40<CR> +nnoremap <leader>5 :vertical resize 50<CR> +nnoremap <leader>6 :vertical resize 60<CR> +nnoremap <leader>7 :vertical resize 70<CR> +nnoremap <leader>8 :vertical resize 80<CR> +nnoremap <leader>9 :vertical resize 90<CR> +nnoremap <leader>0 :vertical resize 100<CR> + + +" Increases the height of a horizontal split. +nnoremap <leader>v1 :resize 5<CR> +nnoremap <leader>v2 :resize 10<CR> +nnoremap <leader>v3 :resize 15<CR> +nnoremap <leader>v4 :resize 20<CR> +nnoremap <leader>v5 :resize 25<CR> +nnoremap <leader>v6 :resize 30<CR> +nnoremap <leader>v7 :resize 35<CR> +nnoremap <leader>v8 :resize 40<CR> +nnoremap <leader>v9 :resize 45<CR> +nnoremap <leader>v0 :resize 50<CR> + + +" BOL and EOL +nnoremap H ^ +vnoremap H ^ +nnoremap L $ +vnoremap L $ + + +" Search for visually selected text +vnoremap // y/<C-r>"<CR>N + + +" trim trailing whitespace on save +" Are there any file type where I wouldn't want this? +autocmd BufWritePre *.{js,py,tpl,less,html,ex,exs,txt,hs,java,rs,ml} :%s/\s\+$//e + + +" Use .gitignore file to populate Ctrl-P +let g:ctrlp_user_command = ['.git', 'cd %s && git ls-files . -co --exclude-standard', 'find %s -type f'] + + +" Ignores dirs and files +let g:ctrlp_custom_ignore = { + \ 'dir': 'node_modules', + \ 'file': '\v\.(exe|dll|png|jpg|jpeg)$' +\} + + +" WIP: Run elixir tests on that line +" TODO: only register binding in *.exs? file extensions +nnoremap <leader>t :call ExTestToggle()<CR> + + +" Jumps from an Elixir module file to an Elixir test file. +fun! ExTestToggle() + if expand('%:e') == "ex" + + let test_file_name = expand('%:t:r') . "_test.exs" + let test_file_dir = substitute(expand('%:p:h'), "/lib/", "/test/", "") + let full_test_path = join([test_file_dir, test_file_name], "/") + + e `=full_test_path` + + elseif match(expand('%:t'), "_test.exs") != -1 + + let test_file_name = expand('%:t:r') + let offset_amt = strlen(test_file_name) - strlen("_test") + let module_file_name = strpart(test_file_name, 0, offset_amt) . ".ex" + let module_file_dir = substitute(expand('%:p:h'), "/test/", "/lib/", "") + let full_module_path = join([module_file_dir, module_file_name], "/") + + e `=full_module_path` + + endif +endfun + + +" Creates intermediate directories and file to match current buffer's filepath +fun! CreateNonExistingDirsAndFile() + ! echo "Creating directory..." && mkdir -p %:p:h && echo "Created directory." && echo "Creating file..." && touch %:t:p && echo "Created file." + + " Write the buffer to the recently created file. + w +endfun diff --git a/users/wpcarro/configs/.config/nvim/templates/boilerplate.c b/users/wpcarro/configs/.config/nvim/templates/boilerplate.c new file mode 100644 index 000000000000..949743d72587 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/templates/boilerplate.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main() { + printf("Hello, world!"); + return 0; +} diff --git a/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs b/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs new file mode 100644 index 000000000000..c83adbc69fa0 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs @@ -0,0 +1,5 @@ +fn main() { + // The statements here will be executed when the compiled binary is called. + + println!("Hello, world!"); +} diff --git a/users/wpcarro/configs/.config/systemd/user/clipmenud.service b/users/wpcarro/configs/.config/systemd/user/clipmenud.service new file mode 100644 index 000000000000..fac317f3f072 --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/clipmenud.service @@ -0,0 +1,18 @@ +[Unit] +Description=Clipmenu daemon + +[Service] +ExecStart=clipmenud +Restart=always +RestartSec=500ms +Environment=DISPLAY=:0 + +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ProtectControlGroups=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= +RestrictRealtime=yes + +[Install] +WantedBy=default.target diff --git a/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service b/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service new file mode 120000 index 000000000000..387f2023d2d2 --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service @@ -0,0 +1 @@ +/usr/local/google/home/wpcarro/.config/systemd/user/clipmenud.service \ No newline at end of file diff --git a/users/wpcarro/configs/.config/systemd/user/lieer-google.service b/users/wpcarro/configs/.config/systemd/user/lieer-google.service new file mode 100644 index 000000000000..2f79ed6ccaa8 --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/lieer-google.service @@ -0,0 +1,7 @@ +[Unit] +Description=Lieer sync for account 'google' + +[Service] +Type=oneshot +ExecStart=/nix/store/n6c4pr4fyrsjfksspkapb7yqc6fzl166-corp-lieer/bin/gmi sync +WorkingDirectory=%h/mail/account.google diff --git a/users/wpcarro/configs/.config/systemd/user/lieer-google.timer b/users/wpcarro/configs/.config/systemd/user/lieer-google.timer new file mode 100644 index 000000000000..a073da25ea82 --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/lieer-google.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Run lieer sync for account 'google' + +[Timer] +OnActiveSec=1 +OnUnitActiveSec=120 + +[Install] +WantedBy=timers.target diff --git a/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer b/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer new file mode 120000 index 000000000000..e9f2cab3bcec --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer @@ -0,0 +1 @@ +/usr/local/google/home/wpcarro/.config/systemd/user/lieer-google.timer \ No newline at end of file diff --git a/users/wpcarro/configs/.gnupg/crls.d/DIR.txt b/users/wpcarro/configs/.gnupg/crls.d/DIR.txt new file mode 100644 index 000000000000..2a29a47b8dbb --- /dev/null +++ b/users/wpcarro/configs/.gnupg/crls.d/DIR.txt @@ -0,0 +1 @@ +v:1: diff --git a/users/wpcarro/configs/.gnupg/export.sh b/users/wpcarro/configs/.gnupg/export.sh new file mode 100755 index 000000000000..571689773b0e --- /dev/null +++ b/users/wpcarro/configs/.gnupg/export.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +# Run this script to export all the information required to transport your GPG +# information. +# Usage: ./export.sh [directory] +# TODO: run this periodically as a job. + +destination="${1:-$(mktemp -d)}" + +if [ ! -d "$destination" ]; then + echo "$destination does not exist. Creating it..." + mkdir -p "$destination" +fi + +gpg --armor --export >"$destination/public.asc" +gpg --armor --export-secret-keys >"$destination/secret.asc" +gpg --armor --export-ownertrust >"$destination/ownertrust.txt" + +echo $(realpath "$destination") diff --git a/users/wpcarro/configs/.gnupg/exported/ownertrust.txt b/users/wpcarro/configs/.gnupg/exported/ownertrust.txt new file mode 100644 index 000000000000..79b727914f84 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/exported/ownertrust.txt @@ -0,0 +1,3 @@ +# List of assigned trustvalues, created Mon 29 Jul 2019 15:01:24 BST +# (Use "gpg --import-ownertrust" to restore them) +7E87921AAC9C514E9341C4F1C7A53CC58D3B1F8C:6: diff --git a/users/wpcarro/configs/.gnupg/exported/public.asc b/users/wpcarro/configs/.gnupg/exported/public.asc new file mode 100644 index 000000000000..8b5547f4c33b --- /dev/null +++ b/users/wpcarro/configs/.gnupg/exported/public.asc @@ -0,0 +1,225 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFk52cEBEADW2uF8AjpGxbd/yrtCguVzl7fWCCo/vZYGTomoHy7K3ru7bQEN +upIBj1ElcsLGxbNLqdEqb17blTOUpaLLxWhEUw38rTpRyepBH0y2u5INDiw9GlpU +uXKnkvaAF2f7DJH24jQA2mLI5Jcgc2M0Kzmuh1Q1foAy3frORBnYlrd9TlSPU7Og +Jj0T20jtZIsIORov2TFC2cEpwa+9jHkNaBK2Bdg5c0SyI2r3TSJq+L7X8Vkf3Hmb +NEWJj286+ElcFP/FyVgRCtSJPjBg/MF0ucukm96cel5qYfK5RkMA/HCyv6xI8iNn +eZj8sJnozDY4rMxFwNkTxIjwH9cCTW0CR9FMsc1wlIe6Zx0ic8Fu7PZCS5MjM8cQ +LnruOunVnb0YodQ+cLde6FlKu7kNUlLJrH5NnuFxjWPxzC63u+/K6CcRV9ilWe5r +so/ImtNfGO1JiCvisYeOqlTYBKceQgjvu5tZtLJoGxH0UzoJARLCLRwyHN8dGqgp +STRd0Ze6LtzYLG0uuedyPNXDKci7GyrVdAmxVIo+eLA1a7n3YCcluGKZlM0IBWx8 +fTKJ16ASTXpK7Hqr3XSf5V7tUcwxiFFtxh5C7kXglyd4QI6Jk6Xp8HlPLvYXNSNj +VYRMHi/ueFI92jlt3kCodD26btgIEfD3e3JxKfHhOtwSoA2i1Hr43qdvtQARAQAB +tCNXaWxsaWFtIENhcnJvbGwgPHdwY2Fycm9AZ21haWwuY29tPokCVAQTAQgAPhYh +BH6HkhqsnFFOk0HE8celPMWNOx+MBQJZOdnBAhsDBQkJZgGABQsJCAcCBhUICQoL +AgQWAgMBAh4BAheAAAoJEMelPMWNOx+MGm8P/RYqv5mnneRbyJ6CgisYn2iIBQvz ++rmpdGDfkFqsd2YqDoGjzEJLVkan+I1oLnKSv5QJqPw1gG7fSv6X7Trov9J+Cma3 +h1bSn2BBiq8L9paWTILYmsrBe7kU9bQjNKFu0qjfvPqkGX6HXO6c81N00Qgie574 +MCByWgPtJTcbPLJodIxu6+aibwNBc3XInL/d0ZbXLs8Fc0+z2/dO7cmzAdE77d5Q +QaG9fGyztiYlZoUS9g3xT4ZulpPqs9zFa04fPvOXWVl+RQjZOYVYW/T8aVRnXohz +3y7tnxOWs8cmCFd91DDR099DZXstAesWllPsdSld18aMjeM2XrzuaWVDYktaraiY +RUdz6ZRPcaCpsIA2RHn++xEg+3q6QRz1J4bsbqEOKys+KlQO9uIgPMIkiMaLFoe5 +nu63XI4EMezrti86ETUxFPFL107P9KZ0gitjUXP0KSbnGQ7jt5FmuZgSAkbCSlis +Ulm8PZ/cmKj9OZysezDKkXFGtyskAELkpToIy48GtyEVIMk+CXcgNydUXDiLnWI8 +VwgmR1Q+hClLYMPvrk7OR6zK8txXsglJItCRUF5fmAn4Q7loh6i/BCfQpHdylO7G +nn7BOEJ0CJ8Hrr4Y785dtswAX8hWMIuzS4mxAHCjqkkfsOObBfLi+XpkZ6lDkfrQ +jAt2KuAjQR58dHDSiQEzBBABCAAdFiEEDxGpiYeei7v9weI2RO9bXoYcCacFAlmu +vnAACgkQRO9bXoYcCacQ4AgAmjDO+8Sd8d+cezwIjZgq1nPPb+/K0KTGsALe7jdF +MDOKwwPKd75mKbAVyJRu8CMEfgFW04YKbkeVp9bLeD2lpMYsIgpNYy5bU6DNCgi5 +QO501sTqeaWc/rlm7Ng5AlF8GIK6FagrPS31eUexxJ62VFozi3EiibKYepgeIUHR +3ukw6+PWBkvOYSQjZ0Uc08nci8UsewDQaQvuDABR+6WbLDYX6PuyUEzV7MPbyzME +QOvGuYpgcAq5gGB6NNe9zFQK0xAQob1UhDlaa1p8VSZyH/RLnyYCdlq0Bmf95PNq +eE8YkmqFCPKNoWwzOa6pJOk/Y2mXhJm/eD4Avmlo122LN4kCMwQQAQgAHRYhBBP5 +Ly1Y78tmaShOtNywW0z4mvxmBQJZvs4vAAoJENywW0z4mvxmIAoQAMM6QLexK9fK +88EZxYC6x+qYkb3rzjaGyO3dzGhfRQTFJ8HtFrWTR5s/m+1ACKFnbf1xo7AWbsYq +jVIxXsqUt9a4jserpaczlzDQLojFCKSGFmfCVV40FwQHL3W+C40xLHLOq93bpHLD +knp38daRzSryW11ev9y0J3to0qX03WpgFKKwT3fQMT5+V4wZNuzOFmWGaPUsuCQh +SQj5VU+p9Q4soIIzu2gaal4vW3/qZgIlkAmkg0FV5iW7mwScWPcF/kPlkHFMj9pG +aTNVgegxtUQBJEXx+VF0vDiOtRnBjE2woVLq1FWGkn7feX4Jvnajqpf6A8dKeNcv +Egfm00HHhhR5f7LTOYTSXWCqhCmIBKGpYlZo/PDGHdlVoRSR5+qN/1kyP7WZg35u ++7XR0paCTR71RwO+oHZHiv97u6P/iPvVWE7aqCJe6kBW6Q8hB4zjLfH6AbjcIN/9 +Vy0k3ALBfNxatkJZAyl/PQpgSpfpAkbk5upBPvOKVGCWsYA5sYRH4MMiwrOuSgCh +nibnUT4jJjgU7hK7iOTJB1mbNEvyMSksgxtbdA4XWee4iv5qS3ZCX+1RUspZk3IS +8NePnaycg+OlI5gMbSEVEmLCat2V3l5KNZRFWpbmNTCo+Mi4t1i8kgJAHRtn0r6t +sIS9beklhQz1p7KZAphYWjl6kO9p80cnuQINBFk52cEBEAC8e3b6SN4t5I/RRmRt +/YbPFyC81yElaPzBM+OFsbRDr9MrtfeDUp/wgcihQIw01HUDlm4F2WjbGwth/8Zs +tEML3CFwtv4V+sYhKqfg+sS5YqzFrFWfZYod8ppFKNMaw9Pcjl70td2egcBDt1SR +51ni4SdhyMt/KOm4mym/Lf4UNyzlYwykbjtb3nsmvxYI/uVdceDv+7vZoW1rapSw +Zj4ZS8+jhgHrO2p5B5TCHdsDJEYQ2SOYeIm8tfqb4oQlTWwjG0frl5eOp/+9HfK1 +q7R049FMAzmd6fbm0jpzxDveDe58qWWq2aj+7HwTZhmvr9l7uFyR+3TL6s9hEALl +B3RGpOy5hGmvwLIAi4qylZuRJzW0tMveDcaDEdMhtyEKF9DYpk1Ug/01uG+PzK8e +TiFyTCN6OAawWIe7pIxRhlk4+CIqRPEprMTfDJxKEUS7RnUYZ76E94FSV9nEqIJj +UWFLq3aTMqSiSN5LAgT6AJKHrR2+cJVHM1cEILvCIugeuw1GUC+Bxs1qaxIpp4SS +xi9Givx8PkKuCukp1J4B8Alx0ZpRELsBEuhZdEN2LP+Hpz7uyD875r8BvEJ+hU20 +Qfnj2wHmKVF+6jBPwrpzZ/gbXuQzfstmrSJCY6p6izfcQSJmbSNpaTsrOvgmaUev +Wuss4bjuG4loSRwFb/7fsPsX9wARAQABiQI8BBgBCAAmFiEEfoeSGqycUU6TQcTx +x6U8xY07H4wFAlk52cECGwwFCQlmAYAACgkQx6U8xY07H4xvTg/+MbWyGFmLe9b8 +AMJtqwX+3EyP44Mo2CeafvmbPSqxoXh1NdOezlEESZU9fHMDY50IA2hpariO/Le3 +Ck8py5NAznCc4avS+gnahcPyhvUaMCskmN4UaRsohMvxKrdAGyRfXZcQqE5Tu6zM +6TxycQkT00qe+PTSQnV2dvGXE4iBFV5kj3NHV7RBC+7sDZ7cYuLHrw1gOaMWeCcF +oQ0l+DW3CNh3klFds/PIfPALjV25+niwOYcenxqp8GVjooWj7xkASkFyZAqusFTa ++/XY2y7+jvdSmm36gfWiPXWdpPiesekPK1NqPGdAtyv1/EKJd+7cYCbkSH6qPJpb +FnpfK/ItVm39OIe7OVUuZYd4lGeFvKK/nDqSQ+9STVar2+n8Wths4C8KJbLdZxGE +QYWKw5aL09tpQRy/skReAt9hVDq3qflODyuWPqS/oDbSGERA2NndkV3LIkU+ZCXI +xsin4IP8XzC8yJjv11PAzM8wmhlXWOdKDIZXMV/2wO+cyM/t9WtfeXUOuk2NyWO4 +nQ+gD3cDPTJS+t8ReKb/bEXSeHiRfgiYuZXcwT5vGmx4HiYgJ7d9i+8Ikcew75zw +EvR+OLXYVICsI9PiQPzqVBfp+2u5saEvmWORrNLLUBQt24R2CF4Y63pLumsCB3gv +n+cUG+sEsct2sSKhQEEU7Yra9tppHlO5Ag0EWT7OXAEQANUBV3/GFcAtsM9XiSGL +GuWs+S8np+A7WRIISsR5BU22u9XqF7P8/5o/ZJIvoNu9EwTkFZYP9pAxx7F+I/62 +x8YbXCU5byiOG0X/RKRafW1j9zJdZHK2jdga2pRUiCexpk43knTMyYUxyNlOFM+r +UKJlMErTaN8PJldD9f7qq+assxFN+rLW+tWwxtcL9WxdCIBBMKE7ldyaIRzKNMbZ +b+mckdP412Ht05sn2BijgHj/co07M0zw+MLWNc9c50wI+/CYBZXerDp7TZoB2HxD +JH9FqQ+ypjZQkNArieMj8IB6o8nZRbOqs+FO3LJf4A2WuHSLb187LpTLGoL/WaGG +YK5ZE/0DuR7lSZmVYt2qaKqzDUJbbETLNJlhEykKI0f4bkhjI9UPjxlFgarDuFOe +V7KkZXJoapda6lwy+W8GDAcrtMasMIgN5lMQdYJllIOJVs0wTJyMbLFN1hcr8oGc +09tLEPr0FO5lxygMHqcNiia1SO34IJF6HaRHZQeX6Hv2M+FHWpZ3fcu9dkY9i9JK +RM0W3x0OLFBNfCAvBVFxat9xmYYJuRqomPAFIlt3hik/Dl3dWOkPLdVoE97T/r1/ +5DDNBpaO/g7XU24bH6ja9/WO8T9g0L4nTLCtdKSaGFxwT0No7jmgTJxJQan7hHDZ ++0CHj8jCUsMNSK587HTWiZFZABEBAAGJBHIEGAEIACYWIQR+h5IarJxRTpNBxPHH +pTzFjTsfjAUCWT7OXAIbAgUJCWYBgAJACRDHpTzFjTsfjMF0IAQZAQgAHRYhBOpX +PxAcncyJjCSg9h7cm2rmkT65BQJZPs5cAAoJEB7cm2rmkT65H2sQAMlGuoA6pKlA +W9L+Mdn3aHaGHzxiLU0mxJZHLPxLru9YNkEF2uDiSzHRMSSbJCujF8O2Z8jg08f2 ++r/ZVHK1wWd/J7zikh/1pMVj9KVNG0JdyqiHuQ02i5vWdBg6lZZku4uUvU616Ynh +PMDVoEQ6QXQ24BhrSBH9B1tgSnIRc9EHzzW5lTF3+qttA1tJETJODzEZGmSlBene +kUxBZVuH7daEa1pRPGNVSa4TmCTkxgYZUIdnIDC7CeDREldAftCvEll37Ewy8QTi +goAPYYZQd9jJ7ywq1qWFiTt6n6IYvjfVm2ttO/3PBtla5FdCW3U0MZyxOiIIpHjh +IgVFWknvOEsKQhaN4rbq7YSMLLZW5Y0ukHk8c3OsRpO5Clc/yl0hHURGtjhvHgSg +kL8z80WhnP+XiMa/vaWIsats1/LGc+uvstmohI4np9+jF/6Byk9Y85ki1ilQszVP +/JEIj2IvH6/OsjcXlgAs8In7EBeixlERLneK9F0D+rb99m21rhkXmy/EQ75yeSV/ +z7sy9suK3SuULqYSuTKuw+mbAY3KOF3JLjQdW1NXCZhqRaxYF+5nMq1f6VDwGcpO +pPSF+9fY6/R5/cyEk8LOY4kS9O9rqc4PIi1ieYoyxezTBdX1aWpZEsAzVDWuJRCd +pwaKM8YF7ofZxEo5QAnfOO/gc1opPVIUMzAP/2md27adc9Qn0AkWf4Gbr5HSbArq +urGjqUa8HCUcishzW/TrWbDtuy0ZplvWv4z1KgqoAAr+U2rZwck8mk02UCmjpaDL +KliTiBAuRei6wgW0y7rLE0S1q9wjoJZemhVrI5C0hu7jIPzl1r/ZRNEqcKtsl6UG ++2zlo0loGiLt+G+p5KpcwuscYGEU0Ekasu+68sual5UUdRXpu8J9ePnqSIZUHanj +mrARW5iv17NGG3ZtGeX+rDZ2G23Ymnc/IOK1Qeyz07GE3OcxaikTk4EvNDJcl33E +uI7Dcauz+1msBxGwkib/BUx686ZI8e3Qa9cxzmzGBAwJGqtuOv0BrKbmT6dJ1FG2 +XtRCKsYDGttvoEM8fnhAeVXLIEidiMg3ri8cE/uhIKQlTCoel/hr6yM0BztxQRIk +PIkFDGSpOq6pknv0KgAxhymJlHmC002e3FAl+B+q71FUthwjs7h110CrI6yZTwbC +La9FZEODAFWkWLghV3iLP0D8HD+rsBxDttpJOC0lndsONIMkb2Xf4ue8pceUehEN +zf+mkS6B5ilfHOhrcY3vkfV/cuF2Zv1kBpjayCbanweeyEIok720eP4RoTu8NJGZ +gD0qLo2iJBW65FRr557CWET+X22k2vDO92PeB6MXdZ+PGNgPWw+SZfY5nUN6hsbm +73dNlPTad6zfMtHRuQINBFk+zuMBEADd0yUpELWiFKezO/GLaqBs5iI5fRvO97pk +5yhROIjaM4xz2tmZMvenO9AdVSchgh6w1CCNMvhbE9MkEakh8qN5hWl0XeN+UarF +XvIx1ARfzmI+Xwz40wNRNHkGMwZipvHQ0oFW2NI+qQaxu1QzX7eGIF1uQPhyw5wg +dQeO3fbAKR7G1YNOiBM5KyEPSFj29fSyPVjhqM5orHyrD3rtHir+978hA0W4yFY3 +0lY7OqaHs2crU4txy30bc2oYL93J5uMDeXmDg+K7NEGeih1vJnh3lF73yh3i6d+t +MxrMyzkhLf8GBD+38QqJ4npkcd4E66g0kB3Q1EYfh4R2eMkYCCcTuRuQVrtIyC81 +r490Kx3WFB2ioIOARk/0NUrnqO0tze4KuLKc4wVpKFCeRkBpN5ZxoF+NJ2DLodg8 +w5G5xkMluLpcUWU+D/LEf52Nu8JBro4BVOv8h+D3MwC8icaQaL4xTVqCBtOvhA3/ +0gxrEJ98g5jpR/Puspa/zQVpY2MP632m6Ctfw6mdHtcq3PKX2sJIF8UKLGhJsAYg +9bUSD4ka+IV1BLXY71b2DFZoTA9WaprG4GDTV4x3PcERq1/LSUBIKl9kHw9DvGzW +uBcQQOwUR564ghGPt4Lxq8fc5G03h2Oou6LZx1lfYHuwQs8iJtiX6EtkVhZxxQX5 +TClQMdVgEwARAQABiQI8BBgBCAAmFiEEfoeSGqycUU6TQcTxx6U8xY07H4wFAlk+ +zuMCGwwFCQlmAYAACgkQx6U8xY07H4yNohAApKlliONc+s6PMtwAOJ3j3NzOCPDy +MOiA24kKHMg4yCUiJDJ+xX2tQs6Jf99IcCIF625nnsUqyRDgdHyDeu4ZTneo1aFB +YMf4fgxqUkEiV7VNxvw2idDfW2Wy0fmyGCdS8UOw9UOjSMURNjfvY/pQlFNG+cWx +ZUfrU2HgXzdAchTlYQnpPwaDaMQE7xsV/Q+VVtWNe9gAuycrGNgPhh4zNAmjqiGN +a+YS0vW4v+TSaA7Y/jMTEJYEz9+60dYy4I64Wv1NWODT03KExQoINrROLwhn/wD8 +AZhyJKBNuAbSZpMXNMD+2QKtqeNxE7HbTQY7Bqx5feBvDkDgr7ox+KyzR3NuXOHQ +CSbmSEQPN3miiGglHGASctp3Fd93PhXzVtiiRAnqfBw7zGDSgdpaNC8z9DAG6iUY +ZtcNz5UoiCHSOqE1vxV38poWDtZLkKuQRXvXy/uNyPcPx2efaDNf/FxH3gM6L7+6 +gfJ9vDMKUI9Xa9A5u3BMR/1Xiehx/GL7kZ16fZDWhJH1iCUcPwu/wSPDCmzGaX3B +O/FT+H1m/Fql8oKWOy82K1zBVMx7cx+b+3/Qlkbx3wGPGtNLPH1m5QFKoPV7z2zP +tM9VziUSLPPlIEbsT3I4lXYdhFsHbGBk++ZbY9kRUTXkNRciqX2NFcFtNSo9RH8F +KOmVBBI+ZQ28HDGJAh8EKAEIAAkFAlm/5kACHQMACgkQx6U8xY07H4yS/xAAiHYW +T+wgYgFR0e0DYNOlz3KeYZwhORc5/ED07qCxUMFkChpBnXbKLzGViiKK3H9FyaFy +fOhuIqb0GgXX4TTYdHShvceBtMfTfeYMLXQC+WaJgIydbjRK978mDBgIDs96ylEj +ErtgP3J/GXTk616nv9VYYjGGNKQVJNxDGCRzfZks3m/gWH/whbctFQXaBOshstra +nGpoR6EEZERpDkDMdLqd1JEhCPK8YUSPwT0LKm2yQpeR3ly5phZiJC7uZVmq518P +f8UoHYhtb6P18kJeMVbrNpEzDTdGCZ6eKC3B+1tfoft8MXF3fEINFzZKqAXKqsHD +sQtVWPshg49J8HNpc0NbQi90+8ph4oVrWDHoDulhGg9xUTlY1fXUye1uDXhVn8gL +rAeLqz6WP2i7jPnNCJgTXw5+e2kAye0rCvKH0pw8a1Aq2iaxvxr0L1MzAgtKaTh/ +AU3K5j8r7YRUdOMUHMGS5CwwdhwNkABM2Sm7FmlZL/BNwmgxekhJSivLL3M6qPY3 +LjcxxJBfe4gk9RRX9/YCgSkKTwvx1Ko9368G4WcxYOSTP3eVol6o0yBqd0rV/P+l +CCgiAZ/ZoVOvi5jmTy2I9flafPzGp51EdH+RS/rGwW1feP5JMg+NULwc1y4kTru6 +pkHTUu3Ol+M2616HU32p8XJi4mDV5qMRWmLn+UCZAQ0EVKyAeQEIALyGS95q8aCp +8rjM35kpabNOhr9hAcdq0DrxwjOWZd5u569X1sS81VjPqoj1jpA+/GgheWeYrNxm +RbMT1fdtd22W5yiNd5TNXF+RMhZYvnT4Mxm3NNggZoriHsnrG4XbtLZZmMTXwF/6 +a/CsaCXYHp4J3YvYnDc/B0fssj37OXQH0SjpBQnU7U1m7mvLXfm7Mh+zi7VTSz45 +WcFyr7Lg1HRN6OzDtbBjn8kuWWMzYIlg+EUZPuHLHoCkjhN6g7AM5eDhQqXvzOcy +lSIk/TIPy7n8EeKrrgfijGQOJF6c6d5n0Hq+6lejT6uL3iHUOKtv8PYGr8vF01ao +vP/EOVTkYQEAEQEAAbQrR3JpZmZpbiBTbWl0aCA8Z3NtaXRoQHNlY3VyaXR5c2Nv +cmVjYXJkLmlvPokBNwQTAQgAIQUCWCuGZQIbAwULCQgHAgYVCAkKCwIEFgIDAQIe +AQIXgAAKCRBE71tehhwJp2lCCACiTsLoGbq44A11+k24oWItbJTrg5pISwKUwfwt +hvik0oQPWfQoz/sr0w/Ie0rUnCuSyOVUXuJZSgzFOjEcwmw1dDv0hsanyt+NZ3SC +r/hSasAOMIeXS7+hyL894E6NKIGDi25+Yhpj1AFneCu9cOoxlEXqynVaiBJbpHIw +atwB5i7ZvUz+krTBjf6wwgLzBi1EHw7IJYhgS1Ye9A/+h+iur7d/4/C6cC31IgBd +r8d8iNbMhqyk66+fhdZ5Vd2QO4DUq4CUgFoakO9X383Jf8azR0zXIPPphJ2QpQzD +sfriUT0J18bP546tknwOsNYlt1XPYwlLvXKljXr1YkRyTdPTtCdHcmlmZmluIFNt +aXRoIDx3aWxkZ3JpZmZpbjQ1QGdtYWlsLmNvbT6JATcEEwEIACEFAlSsgHkCGwMF +CwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQRO9bXoYcCaed+Qf+JSDZ3odMwlnr +bb2kwslduAt9VhRm+dfdIm25nAgxJUxIju8uIgE9v+8dRdGFwrV8pKBYYOCMi8MF +NYuu9zS66wXS4opd/DeYDj2yaN0wBYEfeXMCwVLVDHU7AHrsxQWRSxbcUOi2Mm2s +ig70ZSq2iNicX2f6eUSr/4CjocTP6jOqcHd6Di4odEy/hK6ukCCW8ia1Uujh7JYC +U7quHnuE1N184W2Jf6hUieFC2kE+Nmhix0LsYYe6c1InembHRZ85BpOsWWuE9cS6 +IuVO/jbNZcgS7NkuCHkG7CubPnSZX/EDwmyr9Pd57tr9BANuDNvTGgcbaXhJj5nl +Ix/usDsrRLQxR3JpZmZpbiBTbWl0aCAoS2V5YmFzZSkgPGdsaXR0ZXJzaGFya0Br +ZXliYXNlLmlvPokBNwQTAQgAIQUCWCuHlgIbAwULCQgHAgYVCAkKCwIEFgIDAQIe +AQIXgAAKCRBE71tehhwJpxWTB/0ehZJ1Bjkf7AvtWYn7PEwr1y9aAWHLhAxNNXOE +M5IhXjnpL5o3Pic8DonUrzDVxRsNxaGU8jvAvbQpWgtQXJFi0qgDxS6b1hf5CSlS +kcjqtkcMMqyi7XAydSyCXr5s0sZ2ZBn0tri0AKN7JW4Wd0aXJrP/RmmXNeTTARI/ +LGy5Em/PBFogDPTHRWwJQ5uCaddwev3pcOzNvrSvR0m1JXG+ZtP/Z+c4QQA3YGdT +TSxanK2w9NXTQVToJKO8Lig3ivYNgpbscE0ywrbXVfu3pzB1+9uTa3zd9MmQ0QL9 +mX3RiJeExNE+Vxj5jG+kE8GhcRxXKefXkg+UweaYfkcX8vEEuQENBFSsgHkBCADI +E/6vQg1OW9aGffzp3atrHtCjEHU6ZONE6unlez4CGHZXIZYTAbA0Nmgd3d3JA7wd +d0p48whI/tREFHlBD4lxQBN3wrpmDFVq0OiSLuMSAZaTXrX5ctY4CiHJVOIJUK16 +6zsoQFqvTBW7hYTsmFml1frOZrnyeYD9Hyj1Kkk1kaUkf+JrtnZzcftqD0hFzYHe +645YsLS2ub/ZoXrlV1hznDdIH64TYwlvabvBcZR6Exn6+hByMSbem1nNqB4PN2GV +/dO2OrkolThctGaxVoChDoauA+vfUQRWbpxzMJQHAJ3/PtKMKyMjv0+TTSIO1zsp +i2mayI7XUyXLu5fcTfQxABEBAAGJAR8EGAEIAAkFAlSsgHkCGwwACgkQRO9bXoYc +Cae2+Qf/QWJ+sVhFHNHUjPWSL1o+dSUMIv6qseCGyojGLZxAl9z6IKUng638XMrV +kgAy1aoy91N+HY0IPg45huTvU36uFoD2Hr9dd+ZVftO38jfviiowqu+iPt16sfZq +f9VUTDTJpsLzoxiwq+x5FbJYt2iqDqK30JyQD2EMn5Li0qtR1ohunxR2CE5byNRA +1ymk1BKMDb0tDHl87fCY5+bHZBrG0svqyDsxxK0T4bqRl0gSUfhVA67xcL1C7wG1 +MmtNZM+Ks9AepFlxmRDJnX0XNdaw7P6QtK4igLw5hSiFpVYmdfEyL9W0yn4No4Pj +rJnQvrCvIJqJ+1ANxY9H9ArCl3iF/5kCDQRZvrPlARAA1DjXoVu6jU9Rfojm7iFu +XJm2Suq7W3v4HjsycExn3ZBh2Lh5Jc7EdncPbP3UWnNBI5MlerHS5VMfC1OFzG/Z +IRXZyWIVOu1ajRH39i/8pIxOfcCQ1Y4msN0QntL79Z1qAtOdUGqppViiywgTA7XC +yGU8lvVEx7TlgXdmviRSI2Mm2McbeD/YLdTgqD8+8J00sIW5DUk1n/gUkyU2z4mk +4rwvGLJR2bGv1KIndZzfg7c+fNd2UsXjcJV8+eMRJCjE+xlrviBnJHnrNj75Ps9v +xX4WDX2PQZycS4NEictsFOmWmyiAHWcW8ZOrqsrDbd2QGUxjKrh6kdrZvVhCogsO +StwdVm39TfBykLt3K5jdhU5QK4AyNBbVoikoBtZFyVg2G6e0vbVAYF5NDoy+uDyM +jK9cHeJfMHRPCW+rBkaNh4+i0uj97SML9F1G1s3+dlPSNIofvJvZ69VxJi5w0OCT +vwdgmR/na7DQTBikZK+F0hoZRJAmKgxh0yUMzExYUq9rvdgNeEyIWs3nh6GVPs15 +iZfcRITiGFZsi/BWeSH/ce96qYG+c5UkN1QOvFqMZdneF/uUfUI/qOc3KGmhjW+f +lcO/qqtROO3UisckjvUZL1/80YQISMn96iJpG8mOUrDHiHndSMwErMyyxz3XXYzQ +ys7W2oihWL5iKU+SHmisNA0AEQEAAbQqSnVzdGluIERlTWFyaXMgKGh0dHA0MDIp +IDxqdXN0aW5AZGVtYXIuaXM+iQI+BBMBAgAoBQJZvrPlAhsDBQkDwmcABgsJCAcD +AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDcsFtM+Jr8ZiuBD/9dOkCFWDZWAj1LKBc6 +nKci16H8tazePpvjYeZFxJw2w5NEGgwg1iGsdhKm60EOA8Okh8cEmmfq+AriFPEH +nAbnSePGNeXTRFy7njaApxnRQGaVTV+++B/J/zQTA+2iJXh4gWR2/ip6gmyAGQJK +u63jA/fwoeWqcQDI1FnHqpGEHb6BLeeyRA7iXd3TYTOYpuFJGx57yhZflFssbmwY +3MW3NyWdOYXkWiH1OfujYHuh5du5txiEMvN78q5F18byIaLSkoa3eOM6osGOn8kj +X3iJEAOk+HzMONQd2O59OWmozzyxHicr4rv7LIOeAvL9gi+gpEflT65/AJbgLa5N +JLcvQwAD/iRRv9fs7CSsOlwLyZEVGy0huZ2iyxSguBwkDsHd6yFAr90F8eehV4z6 +DlW3g5UREVQEcUIKW4FEg+E0XMe7tILOcqTzhsMrd3PwMmC/RDPoyOOhJLCLFAg9 +hN+xaFEr4cDUPT1PwudHqQ9u9uqyeH47O3Qi0KJ2IsrWgcjTy3z0setCZDh3APlv +Y9o+Go4ykvMNV/iHwnui/CS/sIX64VKrBq5L+0cq3PnbJfeqxi/Q7CRBko4Zf2h1 +A5SkSM3lwSuLk+zLNu+erS13EjwfainK4eOgFism7lN5CD4Z7VrKxtOTKjozidG0 +N3Ez7edUYQ28NGWJBIMEb6qn/7kCDQRZvrPlARAAoWI61RD8wjDINkiXAtX0jcoG +dvO9oMXvVFWqsGEGivqciifdA5VvB/9jK0YfFQbLvQtkfvcqITuGflBExCK47CDg +lv4AxI0xNkj1jKwgvm/tU6y+Oe1mrw+b64Z/V5naptNnIU4VgVSNsWvZkH2EKxgq +6k+fAoCCwxlctw2JMmbnmUNOiu2miwoiq/Agl8Jfd4xSrAGZn77ZHM+XNLgabKiJ +782E3alCFOXbIftOXIcxgOQWbiiPEUjzCJ5llMdjVnOkn7uP+ZXm3/h7IsrC9/GP +DqSsebGPbNuxgNrDj9HigYPNK6jjZ95bLImaADfd2h/OXA9FYz+HwJ2kBZRNGtDj +FxsmLvqNolu8WZKSjiw7SNK0Ya+55y8KU2iO/G3T5ilAB6nRPliP9aE6IIEzNWgK +9nNM92S34CZQMhOnVjRqH4WEi9i3j/rlSAX4mJLbe2pi6cueSBc53qisBs6H8p5Z +hqPQJfUlVeRxF4ZNKF7wt6dhQcEbi3/IxoABBizIt5DSBybOMLOAB65A5GQkPlJ7 +VyHhzlIoS5RqzwOqg6TCQ4UUtifQqtFXuwVHlHAPhi56U10IiCWJd4hy635Eei6C +WDmGr4+eXTK14h93f79JxIqqve5y7cZgcQ+dQPSBVl19FZnvQUA4/5E8UPI1X3cQ +o8I1542PpL74CcXBZ1kAEQEAAYkCJQQYAQIADwUCWb6z5QIbDAUJA8JnAAAKCRDc +sFtM+Jr8ZqSQEACFW1xrxu8mmXGXpLlXpGx9CFWBfJSFtWBYNjbLZ8Rcdu8FweBt +HVIAmdwYbBHYgT/xQd9Gxg+Z+JmnaAZFi88pN5Pmh2dy53nysLsjYZS8G7p2lKdu +alXrM90PxGpwugNNPVEr//+Bb6ahQgnJQLDY6wz3DuA3L1vk+sBN+00iuGbaW992 +kPRcz2KSDXY0jR3lh3938qBXJR6jbYN2YxHMhfCeK8y9hpNSP5UVUYlLeEjyEIT7 +HgbMwsX8WX8OvL+uacwSzwC1JE8Vn98pIEQgMveZn9ylwuJZp1zv5eSulDsDRWA8 +S8Agjb/fjdQGsck4REiahw3DIPqcIvUFr3yDybB8dTLp509UqK+HLw/bf8QMmpc8 +YazIquk0/HVm0tdijDCgAIw+Dh8LEP1gmCVxynlrHs9ItCjlipuTao8LopjwOiGE +9LnYy19ESF7kUbtyFenmp+FX0WAvlUfhrSfeeM+vR1yD8dJSa1XDI/WafkxBQRuV +sd3cfQIxDn7JGlhwytRkxl7oabxHokc3wCSzu5Sb4ok6A91HPbSNqJ2nKSkbAwQE +u6d5vx6SSO02stvyHPUFLM7zTTNa2B5Vrz9e7eItKEm6taXqzx1e7A8w+kOlP/0p +4oLlBDWgklpqVZcPWtFDsldyNZlxwo5xw9czlTZ+hVuaHdSqwP2NNQegYw== +=5XsB +-----END PGP PUBLIC KEY BLOCK----- diff --git a/users/wpcarro/configs/.gnupg/import.sh b/users/wpcarro/configs/.gnupg/import.sh new file mode 100755 index 000000000000..e698aa3d2bd2 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/import.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +# Run this script to import all of the information exported by `export.sh`. +# Usage: ./import.sh path/to/directory + +gpg --import "$1/public.asc" +gpg --import "$1/secret.asc" +gpg --import-ownertrust "$1/ownertrust.txt" + +# Run this at the end to output some verification +gpg --list-keys diff --git a/users/wpcarro/configs/.gnupg/pubring.kbx b/users/wpcarro/configs/.gnupg/pubring.kbx new file mode 100644 index 000000000000..208fad71b797 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/pubring.kbx Binary files differdiff --git a/users/wpcarro/configs/.gnupg/trustdb.gpg b/users/wpcarro/configs/.gnupg/trustdb.gpg new file mode 100644 index 000000000000..8781b2ad9b05 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/trustdb.gpg Binary files differdiff --git a/users/wpcarro/configs/.sqliterc b/users/wpcarro/configs/.sqliterc new file mode 100644 index 000000000000..7e8b3e3fb4a9 --- /dev/null +++ b/users/wpcarro/configs/.sqliterc @@ -0,0 +1,2 @@ +.mode column +.headers on \ No newline at end of file diff --git a/users/wpcarro/configs/.xsecurelockrc b/users/wpcarro/configs/.xsecurelockrc new file mode 100644 index 000000000000..101495c3ef0b --- /dev/null +++ b/users/wpcarro/configs/.xsecurelockrc @@ -0,0 +1,5 @@ +# Replace the gLinux penguin with a custom image. +XSECURELOCK_LOGO_IMAGE=~/.local/share/static/pickle-rick.jpg + +# Turn this off on laptop (not on desktop). +XSECURELOCK_BLANK_DPMS_STATE=on diff --git a/users/wpcarro/configs/install b/users/wpcarro/configs/install new file mode 100755 index 000000000000..ec94036823c5 --- /dev/null +++ b/users/wpcarro/configs/install @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +configs="$BRIEFCASE/configs" + +(cd "$configs" && stow --target="$HOME" .) diff --git a/users/wpcarro/configs/uninstall b/users/wpcarro/configs/uninstall new file mode 100755 index 000000000000..e082d75ceefa --- /dev/null +++ b/users/wpcarro/configs/uninstall @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +configs="$BRIEFCASE/configs" + +(cd "$configs" && stow --delete --target="$HOME" .) diff --git a/users/wpcarro/default.nix b/users/wpcarro/default.nix new file mode 100644 index 000000000000..ab0afe9bf19d --- /dev/null +++ b/users/wpcarro/default.nix @@ -0,0 +1,4 @@ +_: { + # temporarily commented out while briefcase is being integrated in + # depot. +} diff --git a/users/wpcarro/emacs/.emacs.d/init.el b/users/wpcarro/emacs/.emacs.d/init.el new file mode 100644 index 000000000000..a54ab18351ca --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/init.el @@ -0,0 +1,21 @@ +;; load order is intentional +(require 'wpc-package) +(require 'wpc-misc) +(require 'ssh) +(require 'keyboard) +(require 'irc) +(require 'email) +(require 'keybindings) +(require 'window-manager) +(require 'wpc-ui) +(require 'wpc-dired) +(require 'wpc-org) +(require 'wpc-company) +(require 'wpc-shell) +(require 'wpc-lisp) +(require 'wpc-haskell) +(require 'wpc-elixir) +(require 'wpc-nix) +(require 'wpc-rust) +(require 'wpc-clojure) +(require 'wpc-prolog) diff --git a/users/wpcarro/emacs/.emacs.d/opam-user-setup.el b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el new file mode 100644 index 000000000000..a23addefafe4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el @@ -0,0 +1,145 @@ +;; ## added by OPAM user-setup for emacs / base ## cfd3c9b7837c85cffd0c59de521990f0 ## you can edit, but keep this line +(provide 'opam-user-setup) + +;; Base configuration for OPAM + +(defun opam-shell-command-to-string (command) + "Similar to shell-command-to-string, but returns nil unless the process + returned 0, and ignores stderr (shell-command-to-string ignores return value)" + (let* ((return-value 0) + (return-string + (with-output-to-string + (setq return-value + (with-current-buffer standard-output + (process-file shell-file-name nil '(t nil) nil + shell-command-switch command)))))) + (if (= return-value 0) return-string nil))) + +(defun opam-update-env (switch) + "Update the environment to follow current OPAM switch configuration" + (interactive + (list + (let ((default + (car (split-string (opam-shell-command-to-string "opam switch show --safe"))))) + (completing-read + (concat "opam switch (" default "): ") + (split-string (opam-shell-command-to-string "opam switch list -s --safe") "\n") + nil t nil nil default)))) + (let* ((switch-arg (if (= 0 (length switch)) "" (concat "--switch " switch))) + (command (concat "opam config env --safe --sexp " switch-arg)) + (env (opam-shell-command-to-string command))) + (when (and env (not (string= env ""))) + (dolist (var (car (read-from-string env))) + (setenv (car var) (cadr var)) + (when (string= (car var) "PATH") + (setq exec-path (split-string (cadr var) path-separator))))))) + +(opam-update-env nil) + +(defvar opam-share + (let ((reply (opam-shell-command-to-string "opam config var share --safe"))) + (when reply (substring reply 0 -1)))) + +(add-to-list 'load-path (concat opam-share "/emacs/site-lisp")) +;; OPAM-installed tools automated detection and initialisation + +(defun opam-setup-tuareg () + (add-to-list 'load-path (concat opam-share "/tuareg") t) + (load "tuareg-site-file")) + +(defun opam-setup-add-ocaml-hook (h) + (add-hook 'tuareg-mode-hook h t) + (add-hook 'caml-mode-hook h t)) + +(defun opam-setup-complete () + (if (require 'company nil t) + (opam-setup-add-ocaml-hook + (lambda () + (company-mode) + (defalias 'auto-complete 'company-complete))) + (require 'auto-complete nil t))) + +(defun opam-setup-ocp-indent () + (opam-setup-complete) + (autoload 'ocp-setup-indent "ocp-indent" "Improved indentation for Tuareg mode") + (autoload 'ocp-indent-caml-mode-setup "ocp-indent" "Improved indentation for Caml mode") + (add-hook 'tuareg-mode-hook 'ocp-setup-indent t) + (add-hook 'caml-mode-hook 'ocp-indent-caml-mode-setup t)) + +(defun opam-setup-ocp-index () + (autoload 'ocp-index-mode "ocp-index" "OCaml code browsing, documentation and completion based on build artefacts") + (opam-setup-add-ocaml-hook 'ocp-index-mode)) + +(defun opam-setup-merlin () + (opam-setup-complete) + (require 'merlin) + (opam-setup-add-ocaml-hook 'merlin-mode) + + (defcustom ocp-index-use-auto-complete nil + "Use auto-complete with ocp-index (disabled by default by opam-user-setup because merlin is in use)" + :group 'ocp_index) + (defcustom merlin-ac-setup 'easy + "Use auto-complete with merlin (enabled by default by opam-user-setup)" + :group 'merlin-ac) + + ;; So you can do it on a mac, where `C-<up>` and `C-<down>` are used + ;; by spaces. + (define-key merlin-mode-map + (kbd "C-c <up>") 'merlin-type-enclosing-go-up) + (define-key merlin-mode-map + (kbd "C-c <down>") 'merlin-type-enclosing-go-down) + (set-face-background 'merlin-type-face "skyblue")) + +(defun opam-setup-utop () + (autoload 'utop "utop" "Toplevel for OCaml" t) + (autoload 'utop-minor-mode "utop" "Minor mode for utop" t) + (add-hook 'tuareg-mode-hook 'utop-minor-mode)) + +(defvar opam-tools + '(("tuareg" . opam-setup-tuareg) + ("ocp-indent" . opam-setup-ocp-indent) + ("ocp-index" . opam-setup-ocp-index) + ("merlin" . opam-setup-merlin) + ("utop" . opam-setup-utop))) + +(defun opam-detect-installed-tools () + (let* + ((command "opam list --installed --short --safe --color=never") + (names (mapcar 'car opam-tools)) + (command-string (mapconcat 'identity (cons command names) " ")) + (reply (opam-shell-command-to-string command-string))) + (when reply (split-string reply)))) + +(defvar opam-tools-installed (opam-detect-installed-tools)) + +(defun opam-auto-tools-setup () + (interactive) + (dolist (tool opam-tools) + (when (member (car tool) opam-tools-installed) + (funcall (symbol-function (cdr tool)))))) + +(opam-auto-tools-setup) +;; ## end of OPAM user-setup addition for emacs / base ## keep this line +;; ## added by OPAM user-setup for emacs / tuareg ## b10f42abebd2259b784b70d1a7f7e426 ## you can edit, but keep this line +;; Set to autoload tuareg from its original switch when not found in current +;; switch (don't load tuareg-site-file as it adds unwanted load-paths) +(defun opam-tuareg-autoload (fct file doc args) + (let ((load-path (cons "/home/wpcarro/.opam/default/share/emacs/site-lisp" load-path))) + (load file)) + (apply fct args)) +(when (not (member "tuareg" opam-tools-installed)) + (defun tuareg-mode (&rest args) + (opam-tuareg-autoload 'tuareg-mode "tuareg" "Major mode for editing OCaml code" args)) + (defun tuareg-run-ocaml (&rest args) + (opam-tuareg-autoload 'tuareg-run-ocaml "tuareg" "Run an OCaml toplevel process" args)) + (defun ocamldebug (&rest args) + (opam-tuareg-autoload 'ocamldebug "ocamldebug" "Run the OCaml debugger" args)) + (defalias 'run-ocaml 'tuareg-run-ocaml) + (defalias 'camldebug 'ocamldebug) + (add-to-list 'auto-mode-alist '("\\.ml[iylp]?\\'" . tuareg-mode)) + (add-to-list 'auto-mode-alist '("\\.eliomi?\\'" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocamlrun" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocaml" . tuareg-mode)) + (dolist (ext '(".cmo" ".cmx" ".cma" ".cmxa" ".cmxs" ".cmt" ".cmti" ".cmi" ".annot")) + (add-to-list 'completion-ignored-extensions ext))) +;; ## end of OPAM user-setup addition for emacs / tuareg ## keep this line diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio new file mode 100644 index 000000000000..52bc717e470e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: <stdio.h> +# key: sio +# -- +#include <stdio.h> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib new file mode 100644 index 000000000000..5d44e8ed7989 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: <stdlib.h> +# key: slb +# -- +#include <stdlib.h> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct new file mode 100644 index 000000000000..6e9282f83c79 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: struct +# key: struct +# -- +typedef struct $1 { + $2 +} $1_t; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs new file mode 100644 index 000000000000..8ea7b8f07724 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: Elisp module docs +# key: emd +# -- +;;; `(-> (buffer-file-name) f-filename)` --- $2 -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; $3 + +;;; Code: \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function new file mode 100644 index 000000000000..bfa888d5265d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# expand-env: ((yas-indent-line 'fixed)) +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header new file mode 100644 index 000000000000..bf6e525f8c65 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; $1 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header new file mode 100644 index 000000000000..0f0ad5c4fc4e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Library header +# key: lib +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer new file mode 100644 index 000000000000..2a0bcc33f7bb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Provide footer +# key: elf +# -- +(provide '`(-> (buffer-file-name) f-filename f-no-ext)`) +;;; `(-> (buffer-file-name) f-filename)` ends here \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy new file mode 100644 index 000000000000..95f7d9deecd0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Derive Safe Copy +# key: dsc +# -- +deriveSafeCopy 0 'base ''$1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified new file mode 100644 index 000000000000..4c4db62a8a47 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import qualified +# key: iq +# -- +import qualified $1 as $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn new file mode 100644 index 000000000000..10d194ce41f0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Instance +# key: inst +# -- +instance $1 where + $2 = $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension new file mode 100644 index 000000000000..9d6084acb40d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: language extension +# key: lang +# -- +{-# LANGUAGE $1 #-} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator new file mode 100644 index 000000000000..1ab0d762b611 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Separator +# key: - +# -- +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined new file mode 100644 index 000000000000..7609f801f278 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Undefiend +# key: nd +# -- +undefined \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate new file mode 100644 index 000000000000..3cea6ce003ba --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- +<!doctype html> + +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>$1</title> + <meta name="description" content="$2"> + <meta name="author" content="William Carroll"> + <link rel="stylesheet" href="index.css"> +</head> +<body> + <script src="index.js"></script> +</body> +</html> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main new file mode 100644 index 000000000000..1839a27eb5c0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: public static void main +# key: psvm +# -- +public static void main(String[] args) { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage new file mode 100644 index 000000000000..7f110a9718e4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Define package +# key: defp +# -- +(in-package #:cl-user) +(defpackage #:$1 + (:documentation "$2") + (:use #:cl)) +(in-package #:$1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function new file mode 100644 index 000000000000..b1769cd3d102 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function new file mode 100644 index 000000000000..a3c236821e06 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Typed function +# key: tfn +# -- +(type $1 ($3) $4) +(defun $1 ($2) + "$5" + $6) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix new file mode 100644 index 000000000000..45cb24e2b9e3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix @@ -0,0 +1,13 @@ +# -*- mode: snippet -*- +# name: shell.nix boilerplate +# key: import +# -- +let + briefcase = with import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in stdenv.mkDerivation { + name = "$1"; + buildInputs = [ + $2 + ]; +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet new file mode 100644 index 000000000000..4215b15992b6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Code Snippet +# key: src +# -- +#+BEGIN_SRC $1 +$2 +#+END_SRC \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href new file mode 100644 index 000000000000..ac65ea2e49be --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Org mode URL +# key: href +# -- +[[$1][$2]] \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main new file mode 100644 index 000000000000..4dd22dc0b2da --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Dunder main (__main__) +# key: mn +# -- +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function new file mode 100644 index 000000000000..379ceda1a3a6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +def $1($2): + $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header new file mode 100644 index 000000000000..db48adfec737 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +################################################################################ +# $1 +################################################################################ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init new file mode 100644 index 000000000000..5c407495f53a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: dunder init +# key: ctor +# -- +def __init__(self$1): + $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang new file mode 100644 index 000000000000..0f45ae782d32 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: shebang +# key: shb +# -- +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 new file mode 100644 index 000000000000..3babc730305a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: utf-8 +# key: utf +# -- +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function new file mode 100644 index 000000000000..882c48ded39d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(define ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda new file mode 100644 index 000000000000..b9a684588bc4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda function +# key: ld +# -- +(ฮป ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol new file mode 100644 index 000000000000..254b9fd96b18 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda symbol +# key: l +# -- +ฮป \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function new file mode 100644 index 000000000000..6b4b6a5db2a7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +let $1 = (~$2:$3) => { + $4 +}; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch new file mode 100644 index 000000000000..40f34ff8d1f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Switch statement +# key: sw +# -- +switch ($1) { +| $2 => +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor new file mode 100644 index 000000000000..62834a29ab04 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: exactness +# key: $x +# -- +$Exact<$Call<typeof $1>> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log new file mode 100644 index 000000000000..82ec3fd8e379 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Console.log helper +# key: clg +# -- +console.log($1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn new file mode 100644 index 000000000000..8e35e61fc2c4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: const definition +# key: cn +# -- +const $1 = '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function new file mode 100644 index 000000000000..13f2018f2269 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: const function +# key: cfn +# -- +const $1 = ($2) => { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const new file mode 100644 index 000000000000..2a52c57c75cd --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Destructuring a const +# key: cds +# -- +const { $1 } = $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow new file mode 100644 index 000000000000..187a2efc5a7c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: fa +# -- +=> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function new file mode 100644 index 000000000000..694914a83c95 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: faf +# -- +() => { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured new file mode 100644 index 000000000000..ded3ce163a93 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import destructured +# key: ids +# -- +import { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react new file mode 100644 index 000000000000..0463f5cd5593 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import React dependency (ES6) +# key: ir +# -- +import React from 'react' diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type new file mode 100644 index 000000000000..fcd51f687b61 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import type +# key: ixt +# -- +import type { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y new file mode 100644 index 000000000000..09fa6df50506 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import x from y +# key: ix +# -- +import $1 from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y new file mode 100644 index 000000000000..9f550e300d12 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import y +# key: iy +# -- +import '$1' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test new file mode 100644 index 000000000000..ed382d4f74c4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: Jest describe/test block +# key: dsc +# -- +describe('$1', () => { + test('$2', () => { + + expect($3).toEqual($4) + }) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test new file mode 100644 index 000000000000..12ca2e786ded --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Jest / Jasmine test +# key: tst +# -- +test('$1', () => { + expect($2).toBe($3) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component new file mode 100644 index 000000000000..f2a93a31d96d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: React class extends +# key: clz +# -- +class $1 extends React.Component { + render() { + $2 + } +} + +export default $1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action new file mode 100644 index 000000000000..681c5d0dfdf4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: redux-action +# key: rax +# -- +export const ${1:$$(string-lower->caps yas-text)} = '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action new file mode 100644 index 000000000000..53c6e5fc5ac2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: typed-redux-action +# key: trax +# -- +export const ${1:$$(string-lower->caps yas-text)}: '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' = '`(downcase (buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop new file mode 100644 index 000000000000..4d8e0e3bbd24 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: for-loop +# key: for +# -- +for $1 in $2 { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match new file mode 100644 index 000000000000..bf0e876e2b98 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: match +# key: match +# -- +match $1 { + $2 => $3, +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function new file mode 100644 index 000000000000..efa946bb272f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Create function +# key: fn +# -- +$1() { + $2 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark new file mode 100644 index 000000000000..797781968881 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode checkmark +# key: uck +# -- +โ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark new file mode 100644 index 000000000000..bc3c356a6157 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode ex-mark +# key: ux +# -- +โ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header new file mode 100644 index 000000000000..ae59c7a50f9c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +/******************************************************************************* + * $1 + ******************************************************************************/ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate new file mode 100644 index 000000000000..b791cdf86fe5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- +<!doctype html> + +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>$1</title> + <meta name="description" content="$2"> + <meta name="author" content="William Carroll"> + <link rel="stylesheet" href="index.css"> +</head> +<body> + <script src="index.js"></script> +</body> +</html> diff --git a/users/wpcarro/emacs/.emacs.d/vendor/dired+.el b/users/wpcarro/emacs/.emacs.d/vendor/dired+.el new file mode 100644 index 000000000000..2403b0af9c02 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/dired+.el @@ -0,0 +1,13696 @@ +;;; dired+.el --- Extensions to Dired. +;; +;; Filename: dired+.el +;; Description: Extensions to Dired. +;; Author: Drew Adams +;; Maintainer: Drew Adams (concat "drew.adams" "@" "oracle" ".com") +;; Copyright (C) 1999-2019, Drew Adams, all rights reserved. +;; Created: Fri Mar 19 15:58:58 1999 +;; Version: 2019.04.21 +;; Package-Requires: () +;; Last-Updated: Sun Jul 21 09:47:33 2019 (-0700) +;; By: dradams +;; Update #: 11727 +;; URL: https://www.emacswiki.org/emacs/download/dired%2b.el +;; Doc URL: https://www.emacswiki.org/emacs/DiredPlus +;; Keywords: unix, mouse, directories, diredp, dired +;; Compatibility: GNU Emacs: 20.x, 21.x, 22.x, 23.x, 24.x, 25.x, 26.x +;; +;; Features that might be required by this library: +;; +;; `apropos', `apropos+', `autofit-frame', `avoid', `backquote', +;; `bookmark', `bookmark+', `bookmark+-1', `bookmark+-bmu', +;; `bookmark+-key', `bookmark+-lit', `button', `bytecomp', `cconv', +;; `cl', `cl-lib', `cmds-menu', `col-highlight', `crosshairs', +;; `dired', `dired+', `dired-aux', `dired-loaddefs', `dired-x', +;; `easymenu', `fit-frame', `font-lock', `font-lock+', +;; `format-spec', `frame-fns', `gv', `help+', `help-fns', +;; `help-fns+', `help-macro', `help-macro+', `help-mode', +;; `highlight', `hl-line', `hl-line+', `image', `image-dired', +;; `image-file', `image-mode', `info', `info+', `kmacro', +;; `macroexp', `menu-bar', `menu-bar+', `misc-cmds', `misc-fns', +;; `naked', `pp', `pp+', `radix-tree', `replace', `second-sel', +;; `strings', `syntax', `text-mode', `thingatpt', `thingatpt+', +;; `vline', `w32-browser', `w32browser-dlgopen', `wid-edit', +;; `wid-edit+', `widget'. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: +;; +;; Extensions to Dired. +;; +;; This file extends functionalities provided by standard GNU Emacs +;; files `dired.el', `dired-aux.el', and `dired-x.el'. +;; +;; Key bindings changed. Menus redefined. `diredp-mouse-3-menu' +;; popup menu added. New commands. Some commands enhanced. +;; +;; All of the new functions, variables, and faces defined here have +;; the prefix `diredp-' (for Dired Plus) in their names. +;; +;; +;; Wraparound Navigation +;; --------------------- +;; +;; In vanilla Dired, `dired-next-marked-file' (`M-}' or `* C-n') and +;; `dired-previous-marked-file' (`M-{' or `* C-p') wrap around when +;; you get to the end or the beginning of the Dired buffer. Handy. +;; +;; But the other navigation commands do not wrap around. In `Dired+' +;; they do, provided option `diredp-wrap-around-flag' is non-nil, +;; which it is by default. This means the following commands: +;; +;; `diredp-next-line' - `n', `C-n', `down', `SPC' +;; `diredp-previous-line' - `p', `C-p', `up' +;; `diredp-next-dirline' - `>' +;; `diredp-prev-dirline' - `<' +;; `diredp-next-subdir' - `C-M-n' +;; `diredp-prev-subdir' - `C-M-p' +;; +;; +;; Quick Viewing While Navigating +;; ------------------------------ +;; +;; You can use key `C-down' or `C-up' to navigate to the next or +;; previous file line, respectively, and at the same time show its +;; file in another window. The focus remains on the Dired buffer. +;; A numeric prefix arg means move that many lines first. +;; +;; Names of files and directories that match either of the options +;; `diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps' +;; are skipped. +;; +;; You can use `e' to show the file of the current line. If it is +;; already shown in the same frame, and if Dired is the only other +;; window there, then the file is hidden (its window is deleted). +;; +;; +;; Font-Lock Highlighting +;; ---------------------- +;; +;; If you want a maximum or minimum fontification for Dired mode, +;; then customize option `font-lock-maximum-decoration'. If you want +;; a different fontification level for Dired than for other modes, +;; you can do this too by customizing +;; `font-lock-maximize-decoration'. +;; +;; A few of the user options defined here have an effect on +;; font-locking, and this effect is established only when Dired+ is +;; loaded, which defines the font-lock keywords for Dired. These +;; options include `diredp-compressed-extensions', +;; `diredp-ignore-compressed-flag', `dired-omit-extensions', and +;; `diredp-omit-files-regexp'. This means that if you change the +;; value of such an option then you will see the change only in a new +;; Emacs session. +;; +;; (You can see the effect in the same session if you use `C-M-x' on +;; the `defvar' sexp for `diredp-font-lock-keywords-1', and then you +;; toggle font-lock off and back on.) +;; +;; +;; Act on All Files +;; ---------------- +;; +;; Most of the commands (such as `C' and `M-g') that operate on the +;; marked files have the added feature here that multiple `C-u' use +;; not the files that are marked or the next or previous N files, but +;; *all* of the files in the Dired buffer. Just what "all" files +;; means changes with the number of `C-u', as follows: +;; +;; `C-u C-u' - Use all files present, but no directories. +;; `C-u C-u C-u' - Use all files and dirs except `.' and `..'. +;; `C-u C-u C-u C-u' - use all files and dirs, `.' and `..'. +;; +;; (More than four `C-u' act the same as two.) +;; +;; This feature can be particularly useful when you have a Dired +;; buffer with files chosen from multiple directories. +;; +;; Note that in most cases this behavior is described only in the doc +;; string of function `dired-get-marked-files'. It is generally +;; *not* described in the doc strings of the various commands, +;; because that would require redefining each command separately +;; here. Instead, we redefine macro `dired-map-over-marks' and +;; function `dired-get-filename' in order to achieve this effect. +;; +;; Commands such as `dired-do-load' for which it does not make sense +;; to act on directories generally treat more than two `C-u' the same +;; as two `C-u'. +;; +;; Exceptions to the general behavior described here are called out +;; in the doc strings. In particular, the behavior of a prefix arg +;; for `dired-do-query-replace-regexp' is different, so that you can +;; use it also to specify word-delimited replacement. +;; +;; +;; Act on Marked (or All) Files Here and Below +;; ------------------------------------------- +;; +;; The prefix argument behavior just described does not apply to the +;; `diredp-*-recursive' commands. These commands act on the marked +;; files in the current Dired buffer or on all files in the directory +;; if none are marked. +;; +;; But these commands also handle marked subdirectories recursively, +;; in the same way. That is, they act also on the marked files in +;; any marked subdirectories, found recursively. If such a +;; descendant directory is listed in a Dired buffer then its marked +;; files and subdirs are handled the same way. If there is no Dired +;; buffer that lists a given marked subdirectory then all of its +;; files and subdirs are acted on. +;; +;; For most such here-and-below commands, a prefix argument means +;; ignore all marks. The commands then act on all files in the +;; current Dired buffer and all of its subdirectories, recursively. +;; +;; But here-and-below commands that unmark or change marks act +;; differently for different kinds of prefix argument: +;; +;; * A non-positive prefix arg means ignore subdir markings and act +;; instead on ALL subdirs. +;; +;; * A non-negative prefix arg means do not change marks on subdirs +;; themselves. +;; +;; For example, `M-+ U' removes all marks, including from marked +;; subdirs, recursively. `C-- M-+ U' removes them from all files in +;; all subdirs (marked or not), recursively. `C-9 M-+ U' removes all +;; marks, recursively, except the marks on subdirs themselves. `C-0 +;; M-+ U' acts like those two combined: it descends everywhere, +;; ignoring which subdirs are marked, but it does not remove marks +;; from subdirs themselves. +;; +;; All of the `diredp-*-recursive' commands are on prefix key `M-+', +;; and most are available on submenu `Marked Here and Below' of the +;; `Multiple' menu-bar menu. The commands that unmark and change +;; marks are also in submenu `Here and Below' of menu-bar menu +;; `Marks'. +;; +;; If you use library `Icicles' then you have the following +;; additional commands/keys that act recursively on marked files. +;; They are in the `Icicles' submenu of menu `Multiple' > `Marked +;; Here and Below'. +;; +;; * `M-+ M-s M-s' or `M-s M-s m' - Use Icicles search (and its +;; on-demand replace) on the marked files. +;; +;; * Save the names of the marked files: +;; +;; `M-+ C-M->' - Save as a completion set, for use during +;; completion (e.g. with `C-x C-f'). +;; +;; `M-+ C->' - Add marked names to the names in the current saved +;; completion set. +;; +;; `M-+ C-}' - Save persistently to an Icicles cache file, for +;; use during completion in another session. +;; +;; `icicle-dired-save-marked-to-fileset-recursive' - Like `M-+ +;; C-}', but save persistently to an Emacs fileset. +;; +;; `M-+ C-M-}' - Save to a Lisp variable. +;; +;; +;; In the other direction, if you have a saved set of file names then +;; you can use `C-M-<' (`icicle-dired-chosen-files-other-window') in +;; Dired to open a Dired buffer for just those files. So you can +;; mark some files and subdirs in a hierarchy of Dired buffers, use +;; `M-+ C-}' to save their names persistently, then later use `C-{' +;; to retrieve them, and `C-M-<' (in Dired) to open Dired on them. +;; +;; +;; Image Files +;; ----------- +;; +;; `Dired+' provides several enhancements regarding image files. +;; Most of these require standard library `image-dired.el'. One of +;; them, command `diredp-do-display-images', which displays all of +;; the marked image files, requires standard library `image-file.el'. +;; +;; `Dired+' loads these libraries automatically, if available, which +;; means an Emacs version that supports image display (Emacs 22 or +;; later). (You must of course have installed whatever else your +;; Emacs version needs to display images.) +;; +;; Besides command `diredp-do-display-images', see the commands whose +;; names have prefix `diredp-image-'. And see options +;; `diredp-image-preview-in-tooltip' and +;; `diredp-auto-focus-frame-for-thumbnail-tooltip-flag'. +;; +;; +;; Inserted Subdirs, Multiple Dired Buffers, Files from Anywhere,... +;; ----------------------------------------------------------------- +;; +;; These three standard Dired features are worth pointing out. The +;; third in particular is little known because (a) it is limited in +;; vanilla Dired and (b) you cannot use it interactively. +;; +;; * You can pass a glob pattern with wildcards to `dired' +;; interactively, as the file name. +;; +;; * You can insert multiple subdirectory listings into a single +;; Dired buffer using `i' on each subdir line. Use `C-u i' to +;; specify `ls' switches. Specifying switch `R' inserts the +;; inserted subdirectory's subdirs also, recursively. You can +;; also use `i' to bounce between a subdirectory line and its +;; inserted-listing header line. You can delete a subdir listing +;; using `C-u k' on its header line. You can hide/show an +;; inserted subdir using `$'. You can use `C-_' to undo any of +;; these operations. +;; +;; * You can open a Dired buffer for an arbitrary set of files from +;; different directories. You do this by invoking `dired' +;; non-interactively, passing it a cons of a Dired buffer name and +;; the file names. Relative file names are interpreted relative +;; to the value of `default-directory'. Use absolute file names +;; when appropriate. +;; +;; `Dired+' makes these features more useful. +;; +;; `$' is improved: It is a simple toggle - it does not move the +;; cursor forward. `M-$' advances the cursor, in addition to +;; toggling like `$'. `C-u $' does hide/show all (what `M-$' does in +;; vanilla Dired). +;; +;; `i' is improved in these ways: +;; +;; * Once a subdir has been inserted, `i' bounces between the subdir +;; listing and the subdir line in the parent listing. If the +;; parent dir is hidden, then `i' from a subdir opens the parent +;; listing so it can move to the subdir line there (Emacs 24+). +;; +;; * Vanilla Dired lets you create a Dired listing with files and +;; directories from arbitrary locations, but you cannot insert +;; (`i') such a directory if it is not in the same directory tree +;; as the `default-directory' used to create the Dired buffer. +;; `Dired+' removes this limitation; you can insert any non-root +;; directories (that is, not `/', `c:/', etc.). +;; +;; `Dired+' lets you create Dired buffers that contain arbitrary +;; files and directories interactively, not just using Lisp. Just +;; use a non-positive prefix arg (e.g., `C--') when invoking `dired'. +;; +;; You are then prompted for the Dired buffer name (anything you +;; like, not necessarily a directory name) and the individual files +;; and directories that you want listed. +;; +;; A non-negative prefix arg still prompts you for the `ls' switches +;; to use. (So `C-0' does both: prompts for `ls' switches and for +;; the Dired buffer name and the files to list.) +;; +;; `Dired+' adds commands for combining and augmenting Dired +;; listings: +;; +;; * `diredp-add-to-dired-buffer', bound globally to `C-x D A', lets +;; you add arbitrary file and directory names to an existing Dired +;; buffer. +;; +;; * `diredp-dired-union', bound globally to `C-x D U', lets you +;; take the union of multiple Dired listings, or convert an +;; ordinary Dired listing to an explicit list of absolute file +;; names. With a non-positive prefix arg, you can add extra file +;; and directory names, just as for `diredp-add-to-dired-buffer'. +;; +;; You can optionally add a header line to a Dired buffer using +;; toggle command `diredp-breadcrumbs-in-header-line-mode'. (A +;; header line remains at the top of the window - no need to scroll +;; to see it.) If you want to show the header line automatically in +;; all Dired buffers, you can do this: +;; +;; (add-hook 'dired-before-readin-hook +;; 'diredp-breadcrumbs-in-header-line-mode) +;; +;; Some other libraries, such as `Bookmark+' and `Icicles', make it +;; easy to create or re-create Dired buffers that list specific files +;; and have a particular set of markings. `Bookmark+' records Dired +;; buffers persistently, remembering `ls' switches, markings, subdir +;; insertions, and hidden subdirs. If you use `Icicles' then `dired' +;; is a multi-command: you can open multiple Dired buffers with one +;; `dired' invocation. +;; +;; Dired can help you manage projects. You might have multiple Dired +;; buffers with quite specific contents. You might have some +;; subdirectories inserted in the same Dired buffer, and you might +;; have separate Dired buffers for some subdirectories. Sometimes it +;; is useful to have both for the same subdirectory. And sometimes +;; it is useful to move from one presentation to the other. +;; +;; This is one motivation for the `Dired+' `diredp-*-recursive' +;; commands, which act on the marked files in marked subdirectories, +;; recursively. In one sense, these commands are an alternative to +;; using a single Dired buffer with inserted subdirectories. They +;; let you use the same operations on the files in a set of Dired +;; directories, without inserting those directories into an ancestor +;; Dired buffer. +;; +;; You can use command `diredp-dired-inserted-subdirs' to open a +;; separate Dired buffer for each of the subdirs that is inserted in +;; the current Dired buffer. Markings and Dired switches are +;; preserved. +;; +;; In the opposite direction, if you use `Icicles' then you can use +;; multi-command `icicle-dired-insert-as-subdir', which lets you +;; insert any number of directories you choose interactively into a +;; Dired ancestor directory listing. If a directory you choose to +;; insert already has its own Dired buffer, then its markings and +;; switches are preserved for the new, subdirectory listing in the +;; ancestor Dired buffer. +;; +;; +;; Hide/Show Details +;; ----------------- +;; +;; Starting with Emacs 24.4, listing details are hidden by default. +;; Note that this is different from the vanilla Emacs behavior, which +;; is to show details by default. +;; +;; Use `(' anytime to toggle this hiding. You can use option +;; `diredp-hide-details-initially-flag' to change the default/initial +;; state. See also option `diredp-hide-details-propagate-flag'. +;; +;; NOTE: If you do not want to hide details initially then you must +;; either (1) change `diredp-hide-details-initially-flag' using +;; Customize (recommended) or (2) set it to `nil' (e.g., using +;; `setq') *BEFORE* loading `dired+.el'. +;; +;; If you have an Emacs version older than 24.4, you can use library +;; `dired-details+.el' (plus `dired-details.el') to get similar +;; behavior. +;; +;; +;; Mode-Line +;; --------- +;; +;; The number of files and dirs that are marked with `*', and the +;; number that are flagged for deletion (marked `D') are indicated in +;; the mode-line. When the cursor is on such a line the indication +;; tells you how many more there are. For example, if the cursor is +;; on the line of the third file that is marked `*', and there are +;; seven of them total, then the mode-line shows `3/7*'. +;; +;; The mode-line also indicates, for the current listing (which could +;; be a subdir listing), how many files and dirs are listed. If the +;; cursor is on the 27th file in a listing of 78 files then the +;; mode-line shows 27/78. +;; +;; For counting files and dirs in a listing, option +;; `diredp-count-.-and-..-flag' controls whether to count the lines +;; for `.' and `..'. By default it is nil, meaning they are not +;; counted. +;; +;; +;; If You Use Dired+ in Terminal Mode +;; ---------------------------------- +;; +;; By default, Dired+ binds some keys that can be problematic in some +;; terminals when you use Emacs in terminal mode (i.e., `emacs -nw'). +;; This is controlled by option +;; `diredp-bind-problematic-terminal-keys'. +;; +;; In particular, keys that use modifiers Meta and Shift together can +;; be problematic. If you use Dired+ in text-only terminal, and you +;; find that your terminal does not support such keys, then you might +;; want to customize the option to set the value to `nil', and then +;; bind the commands to some other keys, which your terminal +;; supports. +;; +;; The problematic keys used by Dired+ include these: +;; +;; `M-M' (aka `M-S-m') - `diredp-chmod-this-file' +;; `M-O' (aka `M-S-o') - `diredp-chown-this-file' +;; `M-T' (aka `M-S-t') - `diredp-touch-this-file' +;; `C-M-B' (aka `C-M-S-b') - `diredp-do-bookmark-in-bookmark-file' +;; `C-M-G' (aka `C-M-S-g') - `diredp-chgrp-this-file' +;; `C-M-R' (aka `C-M-S-r') - `diredp-toggle-find-file-reuse-dir' +;; `C-M-T' (aka `C-M-S-t') - `dired-do-touch' +;; `M-+ M-B' (aka `M-+ M-S-b') - +;; `diredp-do-bookmark-dirs-recursive' +;; `M-+ C-M-B' (aka `M-+ C-M-S-b') - +;; `diredp-do-bookmark-in-bookmark-file-recursive' +;; `M-+ C-M-T' (aka `M-+ C-M-S-t') - `diredp-do-touch-recursive' +;; +;; (See also `(info "(org) TTY keys")' for more information about +;; keys that can be problematic in a text-only terminal.) +;; +;; +;; Faces defined here: +;; +;; `diredp-autofile-name', `diredp-compressed-file-suffix', +;; `diredp-date-time', `diredp-deletion', +;; `diredp-deletion-file-name', `diredp-dir-heading', +;; `diredp-dir-priv', `diredp-exec-priv', `diredp-executable-tag', +;; `diredp-file-name', `diredp-file-suffix', `diredp-flag-mark', +;; `diredp-flag-mark-line', `diredp-get-file-or-dir-name', +;; `diredp-ignored-file-name', `diredp-link-priv', +;; `diredp-mode-line-flagged', `diredp-mode-line-marked' +;; `diredp-omit-file-name', `diredp-no-priv', `diredp-number', +;; `diredp-other-priv', `diredp-rare-priv', `diredp-read-priv', +;; `diredp-symlink', `diredp-tagged-autofile-name', +;; `diredp-write-priv'. +;; +;; Commands defined here: +;; +;; `diredp-add-to-dired-buffer', `diredp-add-to-this-dired-buffer', +;; `diredp-do-apply-function', +;; `diredp-do-apply-function-recursive', +;; `diredp-async-shell-command-this-file', +;; `diredp-bookmark-this-file', +;; `diredp-breadcrumbs-in-header-line-mode' (Emacs 22+), +;; `diredp-byte-compile-this-file', `diredp-capitalize', +;; `diredp-capitalize-recursive', `diredp-capitalize-this-file', +;; `diredp-change-marks-recursive' (Emacs 22+), +;; `diredp-chgrp-this-file', `diredp-chmod-this-file', +;; `diredp-chown-this-file', +;; `diredp-compilation-files-other-window' (Emacs 24+), +;; `diredp-compress-this-file', +;; `diredp-copy-abs-filenames-as-kill', +;; `diredp-copy-abs-filenames-as-kill-recursive', +;; `diredp-copy-filename-as-kill-recursive', +;; `diredp-copy-tags-this-file', `diredp-copy-this-file', +;; `diredp-decrypt-this-file', `diredp-delete-this-file', +;; `diredp-describe-autofile', `diredp-describe-file', +;; `diredp-describe-marked-autofiles', `diredp-describe-mode', +;; `diredp-dired-for-files', `diredp-dired-for-files-other-window', +;; `diredp-dired-inserted-subdirs', `diredp-dired-plus-help', +;; `diredp-dired-recent-dirs', +;; `diredp-dired-recent-dirs-other-window', +;; `diredp-dired-this-subdir', `diredp-dired-union', +;; `diredp-do-async-shell-command-recursive', `diredp-do-bookmark', +;; `diredp-do-bookmark-dirs-recursive', +;; `diredp-do-bookmark-in-bookmark-file', +;; `diredp-do-bookmark-in-bookmark-file-recursive', +;; `diredp-do-bookmark-recursive', `diredp-do-chmod-recursive', +;; `diredp-do-chgrp-recursive', `diredp-do-chown-recursive', +;; `diredp-do-copy-recursive', `diredp-do-decrypt-recursive', +;; `diredp-do-delete-recursive', `diredp-do-display-images' (Emacs +;; 22+), `diredp-do-emacs-command', `diredp-do-encrypt-recursive', +;; `diredp-do-find-marked-files-recursive', `diredp-do-grep', +;; `diredp-do-grep-recursive', `diredp-do-hardlink-recursive', +;; `diredp-do-isearch-recursive', +;; `diredp-do-isearch-regexp-recursive', `diredp-do-lisp-sexp' +;; (Emacs 22+), `diredp-do-move-recursive', +;; `diredp-do-paste-add-tags', `diredp-do-paste-replace-tags', +;; `diredp-do-print-recursive', +;; `diredp-do-query-replace-regexp-recursive', +;; `diredp-do-redisplay-recursive', +;; `diredp-do-relsymlink-recursive', `diredp-do-remove-all-tags', +;; `diredp-do-search-recursive', `diredp-do-set-tag-value', +;; `diredp-do-shell-command-recursive', `diredp-do-sign-recursive', +;; `diredp-do-symlink-recursive', `diredp-do-tag', +;; `diredp-do-touch-recursive', `diredp-do-untag', +;; `diredp-do-verify-recursive', `diredp-downcase-recursive', +;; `diredp-downcase-this-file', `diredp-ediff', +;; `diredp-encrypt-this-file', `diredp-fileset', +;; `diredp-fileset-other-window', `diredp-find-a-file', +;; `diredp-find-a-file-other-frame', +;; `diredp-find-a-file-other-window', +;; `diredp-find-file-other-frame', +;; `diredp-find-file-reuse-dir-buffer', +;; `diredp-find-line-file-other-window', +;; `diredp-flag-auto-save-files-recursive', +;; `diredp-flag-region-files-for-deletion', +;; `diredp-grepped-files-other-window', `diredp-grep-this-file', +;; `diredp-hardlink-this-file', `diredp-highlight-autofiles-mode', +;; `diredp-image-dired-comment-file', +;; `diredp-image-dired-comment-files-recursive', +;; `diredp-image-dired-copy-with-exif-name', +;; `diredp-image-dired-create-thumb', +;; `diredp-image-dired-delete-tag', +;; `diredp-image-dired-delete-tag-recursive', +;; `diredp-image-dired-display-thumb', +;; `diredp-image-dired-display-thumbs-recursive', +;; `diredp-image-dired-edit-comment-and-tags', +;; `diredp-image-dired-tag-file', +;; `diredp-image-dired-tag-files-recursive', +;; `diredp-image-show-this-file', `diredp-insert-as-subdir', +;; `diredp-insert-subdirs', `diredp-insert-subdirs-recursive', +;; `diredp-kill-this-tree', `diredp-list-marked-recursive', +;; `diredp-load-this-file', `diredp-mark-autofiles', +;; `diredp-marked', `diredp-marked-other-window', +;; `diredp-marked-recursive', +;; `diredp-marked-recursive-other-window', +;; `diredp-mark-extension-recursive', +;; `diredp-mark-files-containing-regexp-recursive', +;; `diredp-mark-files-regexp-recursive', +;; `diredp-mark-files-tagged-all', `diredp-mark-files-tagged-none', +;; `diredp-mark-files-tagged-not-all', +;; `diredp-mark-files-tagged-some', +;; `diredp-mark-files-tagged-regexp', `diredp-mark-region-files', +;; `diredp-mark-sexp-recursive' (Emacs 22+), +;; `diredp-mark/unmark-autofiles', `diredp-mark/unmark-extension', +;; `diredp-mouse-3-menu', `diredp-mouse-backup-diff', +;; `diredp-mouse-copy-tags', `diredp-mouse-describe-autofile', +;; `diredp-mouse-describe-file', `diredp-mouse-diff', +;; `diredp-mouse-do-bookmark', `diredp-mouse-do-byte-compile', +;; `diredp-mouse-do-chgrp', `diredp-mouse-do-chmod', +;; `diredp-mouse-do-chown', `diredp-mouse-do-compress', +;; `diredp-mouse-do-copy', `diredp-mouse-do-delete', +;; `diredp-mouse-do-grep', `diredp-mouse-do-hardlink', +;; `diredp-mouse-do-load', `diredp-mouse-do-print', +;; `diredp-mouse-do-remove-all-tags', `diredp-mouse-do-rename', +;; `diredp-mouse-do-set-tag-value', +;; `diredp-mouse-do-shell-command', `diredp-mouse-do-symlink', +;; `diredp-mouse-do-tag', `diredp-mouse-do-untag', +;; `diredp-mouse-downcase', `diredp-mouse-ediff', +;; `diredp-mouse-find-line-file-other-window', +;; `diredp-mouse-find-file-other-frame', +;; `diredp-mouse-find-file-reuse-dir-buffer', +;; `diredp-mouse-flag-file-deletion', `diredp-mouse-mark', +;; `diredp-mouse-mark-region-files', `diredp-mouse-mark/unmark', +;; `diredp-mouse-unmark', `diredp-mouse-upcase', +;; `diredp-mouse-view-file', `diredp-move-file' (Emacs 24+), +;; `diredp-multiple-w32-browser-recursive', +;; `diredp-nb-marked-in-mode-name', `diredp-next-dirline', +;; `diredp-next-line', `diredp-next-subdir', `diredp-omit-marked', +;; `diredp-omit-unmarked', `diredp-paste-add-tags-this-file', +;; `diredp-paste-files', `diredp-paste-replace-tags-this-file', +;; `diredp-prev-dirline', `diredp-previous-line', +;; `diredp-prev-subdir', `diredp-print-this-file', +;; `diredp-relsymlink-this-file', +;; `diredp-remove-all-tags-this-file', `diredp-rename-this-file', +;; `diredp-send-bug-report', +;; `diredp-set-bookmark-file-bookmark-for-marked', +;; `diredp-set-bookmark-file-bookmark-for-marked-recursive', +;; `diredp-set-tag-value-this-file', +;; `diredp-shell-command-this-file', `diredp-show-metadata', +;; `diredp-show-metadata-for-marked', `diredp-sign-this-file', +;; `diredp-symlink-this-file', `diredp-tag-this-file', +;; `diredp-toggle-find-file-reuse-dir', +;; `diredp-toggle-marks-in-region', `diredp-touch-this-file', +;; `diredp-unmark-all-files-recursive' (Emacs 22+), +;; `diredp-unmark-all-marks-recursive' (Emacs 22+), +;; `diredp-unmark-autofiles', `diredp-unmark-files-tagged-all', +;; `diredp-unmark-files-tagged-none', +;; `diredp-unmark-files-tagged-not-all', +;; `diredp-unmark-files-tagged-some', `diredp-unmark-region-files', +;; `diredp-untag-this-file', `diredp-upcase-recursive', +;; `diredp-up-directory', `diredp-up-directory-reuse-dir-buffer', +;; `diredp-upcase-this-file', `diredp-verify-this-file', +;; `diredp-visit-next-file', `diredp-visit-previous-file', +;; `diredp-visit-this-file', `diredp-w32-drives', +;; `diredp-w32-drives-mode', `diredp-yank-files', +;; `global-dired-hide-details-mode' (Emacs 24.4+), +;; `toggle-diredp-find-file-reuse-dir'. +;; +;; User options defined here: +;; +;; `diredp-auto-focus-frame-for-thumbnail-tooltip-flag', +;; `diredp-bind-problematic-terminal-keys', +;; `diredp-compressed-extensions', `diredp-count-.-and-..-flag' +;; (Emacs 22+), `diredp-do-report-echo-limit', +;; `diredp-dwim-any-frame-flag' (Emacs 22+), +;; `diredp-image-preview-in-tooltip', `diff-switches', +;; `diredp-hide-details-initially-flag' (Emacs 24.4+), +;; `diredp-highlight-autofiles-mode', +;; `diredp-hide-details-propagate-flag' (Emacs 24.4+), +;; `diredp-ignore-compressed-flag', +;; `diredp-image-show-this-file-use-frame-flag' (Emacs 22+), +;; `diredp-list-file-attributes', `diredp-max-frames', +;; `diredp-move-file-dirs' (Emacs 24+), `diredp-omit-files-regexp' +;; `diredp-prompt-for-bookmark-prefix-flag', +;; `diredp-visit-ignore-extensions', `diredp-visit-ignore-regexps', +;; `diredp-w32-local-drives', `diredp-wrap-around-flag'. +;; +;; Non-interactive functions defined here: +;; +;; `derived-mode-p' (Emacs < 22), `diredp-all-files', +;; `diredp-ancestor-dirs', `diredp-apply-function-to-file-name', +;; `diredp-bookmark', +;; `diredp-create-files-non-directory-recursive', +;; `diredp-delete-dups', `diredp-delete-if', +;; `diredp-delete-if-not', `diredp-directories-within', +;; `diredp-dired-plus-description', +;; `diredp-dired-plus-description+links', +;; `diredp-dired-plus-help-link', `diredp-dired-union-1', +;; `diredp-dired-union-interactive-spec', `diredp-display-image' +;; (Emacs 22+), `diredp-do-chxxx-recursive', +;; `diredp-do-create-files-recursive', `diredp-do-grep-1', +;; `diredp-ensure-bookmark+', `diredp-ensure-mode', +;; `diredp-eval-lisp-sexp' (Emacs 22+), +;; `diredp-existing-dired-buffer-p', `diredp-fewer-than-2-files-p', +;; `diredp-fewer-than-echo-limit-files-p', +;; `diredp-fewer-than-N-files-p', `diredp-fileset-1', +;; `diredp-find-a-file-read-args', +;; `diredp-file-for-compilation-hit-at-point' (Emacs 24+), +;; `diredp-files-within', `diredp-files-within-1', +;; `diredp-fit-frame-unless-buffer-narrowed' (Emacs 24.4+), +;; `diredp-get-confirmation-recursive', `diredp-get-files', +;; `diredp-get-files-for-dir', `diredp-get-subdirs', +;; `diredp-hide-details-if-dired' (Emacs 24.4+), +;; `diredp-hide/show-details' (Emacs 24.4+), +;; `diredp-highlight-autofiles', `diredp-image-dired-required-msg', +;; `diredp-get-image-filename', `diredp-internal-do-deletions', +;; `diredp-invoke-emacs-command', `diredp-invoke-function-no-args', +;; `diredp-list-file', `diredp-list-files', `diredp-looking-at-p', +;; `diredp-make-find-file-keys-reuse-dirs', +;; `diredp-make-find-file-keys-not-reuse-dirs', `diredp-maplist', +;; `diredp-map-over-marks-and-report', `diredp-marked-here', +;; `diredp-mark-files-tagged-all/none', +;; `diredp-mark-files-tagged-some/not-all', +;; `diredp-nonempty-region-p', `diredp-parent-dir', +;; `diredp-paste-add-tags', `diredp-paste-replace-tags', +;; `diredp-read-bookmark-file-args', `diredp-read-command', +;; `diredp-read-expression' (Emacs 22+), +;; `diredp-read-include/exclude', `diredp-read-regexp', +;; `diredp-recent-dirs', `diredp-refontify-buffer', +;; `diredp-remove-if', `diredp-remove-if-not', +;; `diredp-report-file-result', `diredp--reuse-dir-buffer-helper', +;; `diredp-root-directory-p', `diredp-set-header-line-breadcrumbs' +;; (Emacs 22+), `diredp-set-tag-value', `diredp-set-union', +;; `diredp--set-up-font-locking', `diredp-string-match-p', +;; `diredp-tag', `diredp-this-file-marked-p', +;; `diredp-this-file-unmarked-p', `diredp-this-subdir', +;; `diredp-untag', `diredp-visit-ignore-regexp', +;; `diredp-y-or-n-files-p'. +;; +;; Variables defined here: +;; +;; `diredp-bookmark-menu', `diredp-file-line-overlay', +;; `diredp-files-within-dirs-done', `diredp-font-lock-keywords-1', +;; `diredp-hide-details-last-state' (Emacs 24.4+), +;; `diredp-hide-details-toggled' (Emacs 24.4+), +;; `diredp-hide/show-menu', `diredp-images-recursive-menu', +;; `diredp-last-copied-filenames', `diredp-list-files-map', +;; `diredp-loaded-p', `diredp-marks-recursive-menu', +;; `diredp-menu-bar-dir-menu', `diredp-menu-bar-marks-menu', +;; `diredp-menu-bar-multiple-menu', `diredp-menu-bar-regexp-menu', +;; `diredp-menu-bar-single-menu', `diredp-multiple-bookmarks-menu', +;; `diredp-multiple-delete-menu', `diredp-multiple-dired-menu', +;; `diredp-multiple-images-menu', +;; `diredp-multiple-encryption-menu', +;; `diredp-multiple-move-copy-link-menu', +;; `diredp-multiple-omit-menu', `diredp-multiple-recursive-menu', +;; `diredp-multiple-rename-menu', `diredp-multiple-search-menu', +;; `diredp-navigate-menu', `diredp-regexp-recursive-menu', +;; `diredp-re-no-dot', `diredp-single-bookmarks-menu', +;; `diredp-single-encryption-menu', `diredp-single-image-menu', +;; `diredp-single-move-copy-link-menu', `diredp-single-open-menu', +;; `diredp-single-rename-menu', `diredp-w32-drives-mode-map'. +;; +;; Macros defined here: +;; +;; `diredp-mark-if', `diredp-user-error', +;; `diredp-with-help-window'. +;; +;; +;; ***** NOTE: The following macros defined in `dired.el' have +;; been REDEFINED HERE: +;; +;; `dired-map-over-marks' - Treat multiple `C-u' specially. +;; +;; +;; ***** NOTE: The following functions defined in `dired.el' have +;; been REDEFINED or ADVISED HERE: +;; +;; `dired' - Handle non-positive prefix arg. +;; `dired-do-delete' - Display message to warn that marked, +;; not flagged, files will be deleted. +;; `dired-do-flagged-delete' - Display message to warn that flagged, +;; not marked, files will be deleted. +;; `dired-dwim-target-directory' - Uses `diredp-dwim-any-frame-flag'. +;; `dired-find-file' - Allow `.' and `..' (Emacs 20 only). +;; `dired-get-filename' - Test `./' and `../' (like `.', `..'). +;; `dired-get-marked-files' - Can include `.' and `..'. +;; Allow FILTER + DISTINGUISH-ONE-MARKED. +;; `dired-goto-file' - Fix Emacs bug #7126. +;; Remove `/' from dir before compare. +;; (Emacs < 24 only.) +;; `dired-hide-details-mode' - Respect new user options: +;; * `diredp-hide-details-initially-flag' +;; * `diredp-hide-details-propagate-flag' +;; (Emacs 24.4+) +;; `dired-insert-directory' - Compute WILDCARD arg for +;; `insert-directory' for individual file +;; (don't just use nil). (Emacs 23+, and +;; only for MS Windows) +;; `dired-insert-set-properties' - `mouse-face' on whole line. +;; `dired-flag-auto-save-files', `dired-mark-directories', +;; `dired-mark-executables', `dired-mark-files-containing-regexp', +;; `dired-mark-files-regexp', `dired-mark-symlinks' +;; - Use `diredp-mark-if', not `dired-mark-if'. +;; `dired-mark-files-regexp' - Add regexp to `regexp-search-ring'. +;; More matching possibilities. +;; Added optional arg LOCALP. +;; `dired-mark-pop-up' - Delete the window or frame popped up, +;; afterward, and bury its buffer. Do not +;; show a menu bar for pop-up frame. +;; `dired-other-frame' - Handle non-positive prefix arg. +;; `dired-other-window' - Handle non-positive prefix arg. +;; `dired-pop-to-buffer' - Put window point at bob (bug #12281). +;; (Emacs 22-24.1) +;; `dired-read-dir-and-switches' - Non-positive prefix arg behavior. +;; +;;; NOT YET: +;;; ;; `dired-readin-insert' - Use t as WILDCARD arg to +;;; ;; `dired-insert-directory'. (Emacs 23+, +;;; ;; and only for MS Windows) +;; +;; `dired-revert' - Reset `mode-line-process' to nil. +;; `dired-switches-escape-p' - Made compatible with Emacs 20, 21. +;; +;; +;; ***** NOTE: The following functions are included here with little +;; or no change to their definitions. They are here to +;; take advantage of the new definition of macro +;; `dired-map-over-marks': +;; +;; `dired-do-redisplay', `dired-map-over-marks-check', +;; `image-dired-dired-insert-marked-thumbs', +;; `image-dired-dired-toggle-marked-thumbs'. +;; +;; +;; ***** NOTE: The following functions defined in `dired-aux.el' have +;; been REDEFINED HERE: +;; +;; `dired-do-byte-compile', `dired-do-compress', `dired-do-load' - +;; Redisplay only if at most one file is being treated. +;; `dired-do-find-regexp', `dired-do-find-regexp-and-replace' - +;; Prefix arg lets you act on files other than those marked. +;; `dired-do-isearch', `dired-do-isearch-regexp', +;; `dired-do-query-replace-regexp', `dired-do-search' - +;; Use new `dired-get-marked-files'. +;; `dired-insert-subdir-newpos' - If not a descendant, put at eob. +;; `dired-insert-subdir-validate' - Do nothing: no restrictions. +;; `dired-maybe-insert-subdir' - Go back to subdir line if in listing. +;; `dired-handle-overwrite' - Added optional arg FROM, for listing. +;; `dired-copy-file(-recursive)', `dired-hardlink', `dired-query', +;; `dired-rename-file' - You can list (`l') the files involved. +;; +;; +;; ***** NOTE: The following functions defined in `dired-x.el' have +;; been REDEFINED HERE: +;; +;; `dired-copy-filename-as-kill' - +;; Put file names also in var `diredp-last-copied-filenames'. +;; `dired-do-find-marked-files' - +;; Call `dired-get-marked-files' with original ARG. +;; Added optional arg INTERACTIVEP - no error if nil and no files. +;; `dired-do-run-mail' - Require confirmation. +;; `dired-mark-sexp' - 1. Variable `s' -> `blks'. +;; 2. Fixes to `uid' and `gid'. +;; `dired-mark-unmarked-files' (Emacs < 24 only) - Emacs 24+ version. +;; `dired-simultaneous-find-file' - +;; Use separate frames instead of windows if `pop-up-frames' is +;; non-nil, or if prefix arg < 0. +;; +;; +;; ***** NOTE: (Emacs 20 only) The following variable defined in +;; `dired.el' has been REDEFINED HERE: +;; +;; `dired-move-to-filename-regexp' - Recognize file size in k etc. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Change Log: +;; +;; 2019/07/03 dadams +;; dired-mark-unmarked-files: Apply fix for Emacs bug #27465. +;; diredp-mark-if, diredp-mark-sexp(-recursive), dired-mark-unmarked-files: +;; Use char-after, not diredp-looking-at-p. +;; 2019/07/19 dadams +;; diredp-change-marks-recursive, diredp-unmark-all-files-recursive, +;; diredp-mark-files(-containing)-regexp-recursive, diredp-mark-sexp-recursive, diredp-mark-recursive-1: +;; Added missing PREDICATE arg in calls to diredp-get-subdirs. +;; 2019/06/25 dadams +;; diredp-mark-if, diredp-this-file-(un)marked-p: Use regexp-quote for marker char. +;; 2019/06/03 dadams +;; Removed autoload cookie for diredp-omit-files-regexp - it evaluates dired-omit-files, from dired-x.el. +;; Hard-require dired-x.el. (No reason not to.) Removed fboundp guards for it. +;; 2019/04/22 dadams +;; Added diredp-move-files-named-in-kill-ring. Bound to C-w. +;; 2019/04/21 dadams +;; Added redefinitions of dired-do-find-regexp, dired-do-find-regexp-and-replace. +;; diredp-multiple-search-menu: Added "Using TAGS Table" for dired-do-(query-replace|search). +;; 2019/04/20 dadams +;; Added: +;; diredp-map-over-marks-and-report, diredp-do-emacs-command, diredp-invoke-emacs-command, +;; diredp-read-command, diredp-do-lisp-sexp, diredp-eval-lisp-sexp, diredp-report-file-result, +;; diredp-do-report-echo-limit, diredp-fewer-than-N-files-p, diredp-fewer-than-echo-limit-files-p, +;; diredp-apply-function-to-file-name, diredp-invoke-function-no-args, diredp-list-file-attributes. +;; diredp-do-apply-function: Redefine to use diredp-map-over-marks-and-report. +;; diredp-dired-plus-description, diredp-menu-bar-multiple-menu: +;; Added diredp-do-emacs-command, diredp-do-lisp-sexp. +;; diredp-menu-bar-multiple-menu: Reordered items. +;; diredp-list-marked, diredp-*-recursive, diredp-describe-marked-autofiles: +;; Use diredp-list-file-attributes for DETAILS arg interactively. +;; diredp-yank-files, dired-query: Use diredp-list-file-attributes, not harcoded list (5 8). +;; diredp-set-bookmark-file-bookmark-for-marked-recursive: Corrected interactive spec. +;; 2019/04/16 dadams +;; Added: diredp-delete-if. +;; dired-map-over-marks-check: Added &rest argument FUN-ARGS, so FUN can accept arguments. +;; 2019/04/12 dadams +;; dired-get-marked-files: Do not add t to RESULT. Thx to Jeff Spencer for bug report. +;; If all marked is (t) for some reason reset it to nil, per vanilla Emacs 24+. +;; diredp-compressed-extensions: Added .rar, .rev. +;; 2019/04/10 dadams +;; Added diredp-read-expression (forgot it when added diredp-mark-sexp-recursive). +;; diredp-mark-sexp-recursive is thus only for Emacs 22+. +;; 2019/03/20 dadams +;; Added option diredp-omit-files-regexp. +;; Face diredp-omit-file-name: Added strike-through. +;; diredp-font-lock-keywords-1, for face diredp-omit-file-name: +;; Move to file name. Use diredp-omit-files-regexp. Append * for executable flag. Highlight whole line. +;; 2019/03/17 dadams +;; diredp-font-lock-keywords-1: +;; Use just dired-omit-files as regexp - its components already have ^...$. +;; Removed superfluous execute *'s in regexps and superfluous concat for compressed extensions. +;; Face diredp-omit-file-name: Removed :strike-through for default value. +;; 2019/03/16 dadms +;; Added face diredp-omit-file-name. +;; diredp-font-lock-keywords-1: Use face diredp-omit-file-name for dired-omit-files matches. +;; 2019/03/15 dadams +;; diredp-font-lock-keywords-1: Treat dired-omit-files like dired-omit-extensions. +;; 2019/01/27 dadams +;; Added: diredp-mark-files-containing-regexp-recursive. +;; Bound to M-+ % g. Added to diredp-marks-recursive-menu, diredp-regexp-recursive-menu. +;; 2019/01/17 dadams +;; Added: diredp-mark-sexp-recursive. Bound to M-+ M-(, M-+ * (. Added to diredp-marks-recursive-menu. +;; dired-query: Use dired-query-alist only when available. +;; diredp-move-file: Fix format string in error call. +;; diredp-mark-symlinks-recursive: Added missing DETAILS arg for diredp-mark-recursive-1. +;; 2019/01/01 dadams +;; Added: diredp-list-file. +;; Added redefinitions of dired-query, dired-handle-overwrite, dired-copy-file(-recursive), dired-rename-file, +;; dired-hardlink. +;; Added optional arg DETAILS to these functions: diredp-get-(subdirs|files), diredp-y-or-n-files-p, +;; diredp-list-(marked|files), diredp-yank-files, diredp-describe-marked-autofiles, plus all functions with +;; "recursive" in their name except diredp-get-confirmation-recursive. +;; Added optional arg DETAILS. +;; diredp-get-(subdirs|files), diredp-y-or-n-files-p, diredp-list-(marked|files), diredp-yank-files, +;; diredp-describe-marked-autofiles: +;; Added optional arg DETAILS. +;; diredp-list-files: Use dired-list-file, to optionally show details. +;; diredp-yank-files: Non-positive prefix arg shows details now. +;; 2018/12/02 dadams +;; dired-mark-pop-up: Work around Emacs 22 bug in dired-pop-to-buffer which can exit in Dired buffer. +;; 2018/10/17 dadams +;; dired-read-dir-and-switches: Removed mention of icicle-file-sort-first-time-p (no longer used in Icicles). +;; 2018/09/21 dadams +;; diredp-image-dired-edit-comment-and-tags, diredp-w32-drives: +;; Use pop-to-buffer-same-window, not switch-to-buffer. +;; 2018/09/14 dadams +;; Added: diredp-move-file-dirs, diredp-move-file. +;; 2018/06/30 dadams +;; Added: diredp-delete-if-not. +;; 2018/06/16 dadams +;; Added: diredp-visit-ignore-extensions, diredp-visit-ignore-regexps, diredp-visit-next-file, +;; diredp-visit-previous-file, diredp-visit-this-file, diredp-visit-ignore-regexp. +;; Bind the commands to C-down, C-up, e. +;; 2018/03/25 dadams +;; Added: diredp-user-error. +;; Updated for Emacs 27-pretest-2 change in dired-get-marked-files signature. +;; dired-get-marked-files: Added optional arg ERROR-IF-NONE-P. +;; diredp-list-marked, diredp-insert-subdirs, dired-do-(i)search(-regexp), dired-do-query-replace-regexp, +;; dired-do-find-marked-files, diredp-describe-marked-autofiles: +;; Added optional arg INTERACTIVEP. +;; Pass non-nil ERROR-IF-NONE-P to dired-get-marked-files when INTERACTIVEP. (See Emacs bug #30938.) +;; 2018/03/23 dadams +;; Added diredp-mark-if. Removed: redefinition of dired-mark-if. +;; Differences: msg and return value include both number of matches and number of changes. +;; Added redefinitions (use diredp-mark-if) of dired-flag-auto-save-files, +;; dired-mark-(files-containing-regexp|symlinks|directories|executables). +;; Everywhere: Use diredp-mark-if, not dired-mark-if. +;; 2018/03/03 dadams +;; diredp-delete-dups: defalias the symbol, not its symbol-function (dunno why I did the latter). +;; 2018/02/28 dadams +;; Added: diredp-last-copied-filenames, diredp-copy-abs-filenames-as-kill-recursive, +;; and redefinition of vanilla diredp-last-copied-filenames. +;; diredp-copy-abs-filenames-as-kill: Use diredp-ensure-mode in interactive spec. +;; diredp-copy-filename-as-kill-recursive: Update diredp-last-copied-filenames with filenames string. +;; diredp-yank-files: Require confirmation for pasting, using diredp-y-or-n-files-p. +;; Get file names from variable diredp-last-copied-filenames, not kill-ring. +;; Added NO-CONFIRM-P arg. +;; diredp-ensure-mode: Added doc string. +;; diredp-do-grep, diredp-do-grep-recursive: Changed bindings to C-M-G and M-+ C-M-G, due to M-g conflict. +;; 2018/02/27 dadams +;; Added: diredp-copy-abs-filenames-as-kill, diredp-yank-files (aka diredp-paste-files) (bound to C-y). +;; diredp-menu-bar-multiple-menu: Added diredp-copy-abs-filenames-as-kill. +;; diredp-menu-bar-dir-menu: Added diredp-yank-files. +;; 2018/01/11 dadams +;; diredp-get-files: +;; Set IGNORE-MARKS-P to non-nil if nothing marked here. (It was not getting all if nothing marked.) +;; diredp-marked-recursive(-other-window): +;; Corrected interactive spec, which was missing nil DIRNAME arg. Corrected body: use DIRNAME. +;; diredp-get-files-for-dir, diredp-do-bookmark-dirs-recursive, diredp-change-marks-recursive, +;; diredp-unmark-all-files-recursive, diredp-mark-files-regexp-recursive, diredp-mark-recursive-1, +;; diredp-do-delete-recursive: +;; Factor out (dired-buffers-for-dir (expand-file-name directory)). +;; 2018/01/03 dadams +;; dired-mark-files-regexp: Corrected doc string wrt prefix args. Thx to John Mastro. +;; diredp-do-grep-recursive: Removed unused optional arg IGNORE-MARKS-P. +;; diredp-marked-recursive(-other-window): Moved handling of optional arg from interactive spec to body. +;; 2018/01/02 dadams +;; Added: diredp-flag-auto-save-files-recursive. Bound to M-+ #. +;; diredp-get-file-or-dir-name, diredp-marked-here: Doubled backslashes to escape dots. +;; diredp-marked-here: Fixed regexp to match only double-dot, not single-dot. +;; diredp-flag-auto-save-files-recursive: Updated to include more M-+ keys. +;; diredp-marks-recursive-menu: Added diredp-flag-auto-save-files-recursive. +;; 2017/12/31 dadams +;; diredp-get-files-for-dir: Pass non-nil NO-DOT-DOT-P arg to diredp-marked-here. +;; dired-get-marked-files: Allow use of FILTER and DISTINGUISH-ONE-MARKED together. +;; diredp-marked-here: Added optional arg NO-DOT-DOT-P. +;; diredp-change-marks-recursive, diredp-unmark-all-files-recursive: Removed unused vars include-dirs, files. +;; 2017/12/30 dadams +;; Added: diredp-change-marks-recursive, diredp-unmark-all-files-recursive, diredp-unmark-all-marks-recursive. +;; Bound to M-+ * c, M-+ M-DEL, M-+ U, respectively. +;; diredp-menu-bar-marks-menu: Rename item Change Marks to Change Mark. +;; diredp-marks-recursive-menu, diredp-multiple-recursive-menu: +;; Added diredp-change-marks-recursive, diredp-unmark-all-(files|marks)-recursive. +;; 2017/12/21 dadams +;; Added: diredp-mark-recursive-1. Forgot to add it last June. +;; 2017/12/17 dadams +;; Removed: diredp-display-graphic-p. +;; Do not use diredp-display-graphic-p to allow binding diredp-bind-problematic-terminal-keys by default. +;; 2017/11/25 dadams +;; diredp-nb-marked-in-mode-name: Wrap last :eval sexp in save-excursion. +;; Protect Call dired-current-directory only when dired-subdir-alist. +;; 2017/10/23 dadams +;; Added: diredp-count-.-and-..-flag, diredp--reuse-dir-buffer-helper. +;; Removed: diredp-mouse-find-file. +;; diredp-find-file-reuse-dir-buffer, diredp-mouse-find-file-reuse-dir-buffer, +;; diredp-up-directory-reuse-dir-buffer: +;; Use diredp--reuse-dir-buffer-helper. +;; diredp-find-file-reuse-dir-buffer: Changed logic: do find-alternate-file only if target is a dir not in +;; Dired and current Dired buffer is in only this window. +;; diredp-mouse-find-file-reuse-dir-buffer: Added optional args FIND-FILE-FUNC and FIND-DIR-FUNC. +;; diredp-up-directory, diredp-up-directory-reuse-dir-buffer: Pass OTHER-WINDOW arg to diredp-w32-drives. +;; diredp-nb-marked-in-mode-name: Show also number of lines in current listing, and listing-relative lineno, +;; respecting diredp-count-.-and-..-flag. +;; diredp-find-a-file*: Added autoload cookies. +;; 2017/08/18 dadams +;; Fixed emacswiki URLs everywhere. They changed the locations and changed http to https. +;; 2017/06/30 dadams +;; Added: diredp-bind-problematic-terminal-keys, diredp-display-graphic-p. +;; Guard bindings of problematic keys with diredp-display-graphic-p & diredp-bind-problematic-terminal-keys. +;; Documented problematic keys for terminal mode in commentary. +;; 2017/06/23 dadams +;; Added: diredp-read-regexp (removed alias to read-regexp), diredp-marks-recursive-menu, +;; diredp-mark-executables-recursive (bound to M-+ * *), +;; diredp-mark-directories-recursive (bound to M-+ * /), +;; diredp-mark-extension-recursive (bound to M-+ * .), +;; diredp-mark-autofiles-recursive (bound to M-+ * B), +;; diredp-mark-executables-recursive (bound to M-+ * *), +;; diredp-mark-directories-recursive (bound to M-+ * /), +;; diredp-mark-symlinks-recursive (bound to M-+ * @), +;; Bind diredp-mark-autofiles to * B. +;; diredp-marked-here: Bind dired-marker-char to ?*. +;; diredp-mark-files-regexp-recursive: Better msgs - show total count. +;; Everywhere: Use diredp-looking-at, not looking-at. Use diredp-read-regexp, not dired-read-regexp. +;; 2017/05/30 dadams +;; Fixed typo: direp--set-up-font-locking -> diredp--set-up-font-locking. +;; 2017/05/22 dadams +;; Added: direp--set-up-font-locking. +;; Use direp--set-up-font-locking instead of lambda in dired-mode-hook. +;; 2017/04/09 dadams +;; Version 2017.04.09. +;; Added: diredp-multiple-move-copy-link-menu, diredp-multiple-rename-menu, diredp-multiple-dired-menu, +;; diredp-multiple-omit-menu, diredp-multiple-delete-menu, diredp-single-bookmarks-menu, +;; diredp-single-encryption-menu, diredp-single-image-menu, diredp-single-open-menu, +;; diredp-single-move-copy-link-menu, diredp-single-rename-menu. +;; Moved single menu items there. +;; Renamed: diredp-menu-bar-encryption-menu to diredp-multiple-encryption-menu, +;; diredp-menu-bar-mark-menu to diredp-menu-bar-marks-menu, +;; diredp-menu-bar-operate-menu to diredp-menu-bar-multiple-menu, +;; diredp-menu-bar-operate-bookmarks-menu to diredp-multiple-bookmarks-menu, +;; diredp-menu-bar-operate-recursive-menu to diredp-multiple-recursive-menu, +;; diredp-menu-bar-operate-search-menu to diredp-multiple-search-menu, +;; diredp-menu-bar-images-menu to diredp-multiple-images-menu, +;; diredp-menu-bar-images-recursive-menu to diredp-images-recursive-menu, +;; diredp-menu-bar-immediate-menu to diredp-menu-bar-single-menu, +;; diredp-menu-bar-regexp-recursive-menu to diredp-regexp-recursive-menu, +;; diredp-menu-bar-subdir-menu to diredp-menu-bar-dir-menu. +;; Added dired-do-rename to diredp-multiple-rename-menu. +;; diredp-nonempty-region-p: Ensure (mark) also. +;; 2017/03/30 dadams +;; Moved key bindings to end of file. Moved defgroup before defcustoms. +;; Bind dired-multiple-w32-browser to C-M-RET, diredp-multiple-w32-browser-recursive to M-+ C-M-RET. +;; 2017/03/29 dadams +;; Added: diredp-dired-union-other-window, diredp-add-to-dired-buffer-other-window. +;; diredp-dired-union-1: Added optional arg OTHERWIN. +;; diredp-dired-plus-description: Updated doc string. +;; diredp-menu-bar-subdir-menu: Added diredp-dired-for-files. +;; Bind diredp-w32-drives to :/, diredp-dired-inserted-subdirs to C-M-i. +;; Bind diredp-add-to-dired-buffer to C-x D A (not C-x E), diredp-dired-union to C-x D U (not C-x D), +;; diredp-fileset to C-x D S (not C-M-f), diredp-dired-recent-dirs to C-x D R (not C-x R), +;; diredp-dired-for-files to C-x D F, plus other-window versions. +;; 2017/03/24 dadams +;; Added defalias for dired-read-regexp. +;; diredp-mouse-3-menu: Removed second arg to mouse-save-then-kill. +;; 2017/02/20 dadams +;; diredp-(next|previous)-line, diredp-(next|prev)-dirline, diredp-(next|prev)-subdir: +;; Update interactive spec to use (in effect) ^p for prefix arg (for shift-select-mode). +;; 2017/01/12 dadams +;; dired-mark-files-regexp: Swapped prefix-arg behavior for relative and absolute name matching. +;; 2017/01/01 dadams +;; dired-mark-files-regexp: Fix to prompt for no prefix arg. +;; 2016/12/28 dadams +;; dired-mark-files-regexp: Corrected prompt string for Mark/UNmark. Thx to Tino Calancha. +;; 2016/11/20 dadams +;; diredp-menu-bar-operate-search-menu: Added dired-do-find-regexp and dired-do-find-regexp-and-replace. +;; Bind dired-do-search to M-a and dired-do-query(-replace)-regexp to M-q. +;; diredp-dired-plus-description: Added dired-do-find-regexp and dired-do-find-regexp-and-replace. +;; 2016/10/12 dadams +;; diredp-compressed-extensions: Added extensions .xz and .lzma. Thx to xuhdev (https://www.topbug.net/). +;; 2016/09/20 dadams +;; Emacs 25.1: Bind M-z to dired-do-compress-to (replaces c). (Emacs bug #24484.) +;; diredp-menu-bar-operate-menu: Added item: Compress to (dired-do-compress-to). +;; 2016/09/15 dadams +;; Added: diredp-max-frames. +;; dired-do-find-marked-files: Pass non-nil ARG to dired-get-marked-files only if it is a cons. +;; Clarified doc string wrt prefix arg. +;; dired-simultaneous-find-file: Require confirmation if more files than diredp-max-frames. +;; diredp-do-find-marked-files-recursive: Clarified doc string wrt prefix arg. +;; Thx to Tino Calancha. +;; 2016/09/14 dadams +;; diredp-dired-plus-description: Added entry for dired-hide-details-mode - ( key. +;; 2016/08/26 dadams +;; diredp-y-or-n-files-p: pop-to-buffer only when the buffer was created. +;; Update wrt vanilla (scroll actions). +;; diredp-do-query-replace-regexp-recursive: +;; Call diredp-get-confirmation-recursive. +;; Use only diredp-get-files, not dired-get-marked-files. +;; Non-positive prefix arg means DELIMITED. +;; 2016/08/08 dadams +;; diredp-menu-bar-mark-menu: +;; Added: dired-mark-files-containing-regexp, dired-mark-sexp, image-dired-mark-tagged-files, +;; 2016/05/28 dadams +;; diredp-mark-files-regexp-recursive: Use nil for dired-get-filename LOCALP arg. +;; dired-mark-files-regexp: Corrected doc string: absolute filename matching by default. +;; 2016/05/24 dadams +;; dired-mark-files-regexp: Added optional arg LOCALP, so can mark/unmark matching different file-name forms. +;; 2016/05/15 dadams +;; Added: diredp-bookmark-menu, diredp-hide/show-menu, diredp-navigate-menu. +;; Move insert after revert and rename it to Insert/Move-To This Subdir. Move create-directory before revert. +;; 2016/04/29 dadams +;; diredp-next-line: Respect goal-column. +;; 2016/01/24 dadams +;; Added: diredp-ensure-bookmark+, diredp-mark-autofiles, diredp-unmark-autofiles, +;; diredp-mark/unmark-autofiles, diredp-describe-autofile, diredp-show-metadata, +;; diredp-mouse-describe-autofile, diredp-describe-marked-autofiles, diredp-show-metadata-for-marked +;; Soft-require help-fns+.el (Emacs 22+) or help+20.el (Emacs 20-21). +;; Add to menu-bar menus: +;; diredp-(un)mark-autofiles, diredp-describe-autofile, diredp-describe-marked-autofiles. +;; diredp-menu-bar-immediate-menu: Add diredp-describe-file only if defined. +;; Bind diredp-describe-file to keys only if defined. +;; Use diredp-ensure-bookmark+ everywhere, instead of its definition. +;; diredp(-mouse)-describe-file: Define only if describe-file is defined. Removed raising error if not. +;; diredp-mouse-3-menu: Use diredp-describe-autofile if diredp-describe-file is not defined. +;; diredp-dired-plus-description: Add diredp-mouse-describe-autofile, when bound. +;; dired-mark-if: Do not count non-changes. +;; 2015/12/15 dadams +;; diredp-font-lock-keywords-1: Follow # with optional [/ ], for face diredp-number. Thx to Tino Calancha. +;; 2015/11/10 dadams +;; diredp-fileset(-other-window): Separate error msgs for unloaded filesets.el and empty filesets-data. +;; 2015/10/02 dadams +;; dired-mark-sexp: Like vanilla, skip extended attributes marker before setting NLINK. Thx to Tino Calancha. +;; 2015/09/29 dadams +;; diredp-delete-this-file: Redefined to use delete-file instead of dired-do-delete. +;; 2015/09/07 dadams +;; diredp-font-lock-keywords-1: Do not test diredp-ignore-compressed-flag when highlighting file names. +;; Use separate entries for compressed and non-compressed file names. +;; Added missing \\| before ignored compressed extensions. +;; 2015/09/06 dadams +;; diredp-compressed-extensions: Added .tgz. Removed duplicate .gz. +;; diredp-font-lock-keywords-1: Use regexp-opt where possible, instead of mapcar regexp-quote. +;; 2015/09/05 dadams +;; Added: diredp-compressed-extensions, diredp-ignore-compressed-flag, diredp-compressed-file-name, +;; diredp-dir-name. +;; diredp-font-lock-keywords-1: +;; Allow spaces in symlink names. Highlight compressed-file names, if diredp-ignore-compressed-flag. +;; Use diredp-compressed-extensions instead of hardcoding extensions. +;; Highlight d with diredp-dir-priv (fix). +;; Treat l in third column the same as - and d there. +;; Highlight whole line for D-flagged files, with face diredp-deletion-file-name. +;; Thx to Nick Helm. +;; 2015/08/30 dadams +;; dired-mark-sexp: Updated per Emacs 25 code. +;; 2015/07/30 dadams +;; diredp-fileset(-other-window): Changed key binding from C-x F to C-x C-M-f (conflicted with find-function). +;; 2015/06/24 dadams +;; Added: diredp-parent-dir, diredp-breadcrumbs-in-header-line-mode, diredp-set-header-line-breadcrumbs. +;; 2015/06/06 dadams +;; Added dired-other-(frame|window). +;; diredp-font-lock-keywords-1: +;; Use dired-re-maybe-mark and dired-re-inode-size for permission matchings and directory names. +;; dired(-other-(frame|window)) advice: +;; Add interactive spec, to handle arg <= 0 (broken by change to dired-read-dir-and-switches 2015/02/02). +;; diredp-dired-for-files: Typo: pass empy string. +;; 2015/06/05 dadams +;; Added: diredp-grepped-files-other-window as alias for diredp-compilation-files-other-window. +;; diredp-compilation-files-other-window: Added SWITCHES optional arg (prefix arg). +;; 2015/06/04 dadams +;; diredp-dired-for-files(-other-window): +;; Updated to fit change to dired-read-dir-and-switches made 2015/02/02: addition of READ-EXTRA-FILES-P. +;; Use prefix arg to prompt for switches. +;; 2015/05/31 dadams +;; Added: diredp-image-show-this-file,diredp-image-show-this-file-use-frame-flag, diredp-get-image-filename. +;; image-dired-dired-toggle-marked-thumbs, diredp-menu-bar-immediate-menu [image]: +;; Use diredp-get-image-filename. +;; Bound diredp-image-show-this-file to C-t I. +;; diredp-menu-bar-immediate-image-menu: Added diredp-image-show-this-file and dired-find-file. +;; Added autoload cookies for image commands. +;; 2015/04/16 dadams +;; Added: diredp-do-apply-function, diredp-do-apply-function-recursive. Added to menus. Bind to @, M-+ @. +;; dired-do-query-replace-regexp: Handle nil ARG properly. +;; 2015/03/26 dadams +;; Added: redefinitions of dired-do-isearch, dired-do-isearch-regexp, dired-do-query-replace-regexp, +;; dired-do-search, to handle multi-C-u. +;; Added: dired-nondirectory-p (Emacs 20), diredp-refontify-buffer. +;; dired-do-byte-compile, dired-do-load, : Corrected interactive spec, to treat more than two C-u as two. +;; dired-after-readin-hook: Add diredp-refontify-buffer. In particular, this ensures that reverting Dired +;; for a listing of explicit file names gets refontified. (Just turn-on-font-lock does not refontify.) +;; 2015/03/24 dadams +;; Added: diredp-compilation-files-other-window, diredp-file-for-compilation-hit-at-point. +;; 2015/03/06 dadams +;; Renamed: diredp-menu-bar-recursive-marked-menu to diredp-menu-bar-operate-recursive-menu. +;; Added: diredp-do-delete-recursive: M-+ D. Added to diredp-menu-bar-operate-recursive-menu. +;; Added: diredp-mark-files-regexp-recursive: M-+ % m. Added to diredp-menu-bar-regexp-recursive-menu. +;; 2015/03/04 dadams +;; Added: diredp-dwim-any-frame-flag, (redefinition of) dired-dwim-target-directory. +;; 2015/02/22 dadams +;; diredp-bookmark: Corrected for use without Bookmark+ - bookmark-store signature. +;; Pass correct value to bmkp-autofile-set for its MSG-P arg. +;; diredp-mouse-do-bookmark: Do not pass non-nil NO-MSG-P arg to diredp-bookmark. +;; 2015/02/03 dadams +;; Added: diredp-add-to-this-dired-buffer. +;; Removed: diredp-add-to-dired-buffer-other-window, diredp-dired-union-other-window. +;; diredp-dired-union-1: Removed optional arg OTHER-WINDOW-P. +;; diredp-menu-bar-subdir-menu: Added diredp-add-to-this-dired-buffer. +;; dired-read-dir-and-switches, diredp-dired-union-interactive-spec: +;; Added optional arg DIRED-BUFFER. If nil, use current buffer name as default when reading buffer name. +;; 2015/02/02 dadams +;; Added: diredp-add-to-dired-buffer, diredp-add-to-dired-buffer-other-window, diredp-set-union, +;; diredp-existing-dired-buffer-p. +;; Bind diredp-add-to-dired-buffer(-other-window) globally to C-x E, C-x 4 E. +;; diredp-dired-union(-other-window): +;; Added args DIRNAME and EXTRA. Pass them to diredp-dired-union-1. Moved "UNION" to *-interactive-spec. +;; Pass values for new args NO-DIRED-BUFS and READ-EXTRA-FILES-P to diredp-dired-union-interactive-spec. +;; diredp-dired-union-interactive-spec: +;; Added args NO-DIRED-BUFS and READ-EXTRA-FILES-P. Use (updated) dired-read-dir-and-switches. +;; Delete dead buffers from dired-buffers. Remove DIRNAME buffer as candidate. +;; Apply expand-file-name to default-directory. Return list of DIRNAME BUFS SWITCHES EXTRA-FILES. +;; diredp-dired-union-1: +;; Added args DIRED-NAME and EXTRA. +;; For existing Dired buffer whose dired-directory is a cons: +;; Include its current listing. Replace buffer with new one of same name, after deleting its window. +;; dired-read-dir-and-switches: +;; Added arg READ-EXTRA-FILES-P. +;; If chosen Dired buffer exists and is an ordinary listing then start out with its directory-files. +;; diredp-dired-union, diredp-fileset, diredp-dired-recent-dirs: Bind globally, not just in Dired mode. +;; 2015/01/30 dadams +;; dired-read-dir-and-switches: Remove any killed buffers from dired-buffers, before using for completion. +;; 2014/10/25 dadams +;; diredp-dired-union-interactive-spec: Typo: quote buffer-name-history. Pass other-window STRING. +;; diredp-dired-union-other-window: Pass other-window STRING. +;; dired-read-dir-and-switches: Include STRING for reading buffer name also. +;; dired (defadvice): Corrected doc string for prefix arg >= and <= 0. +;; 2014/10/15 dadams +;; diredp-hide-details-initially-flag: +;; Added :set, to ensure that diredp-hide-details-last-state is kept up-to-date. +;; 2014/09/28 dadams +;; Added: diredp-recent-dirs, diredp-read-include/exclude, diredp-root-directory-p, diredp-remove-if. +;; diredp-dired-recent-dirs(-other-window): Added optional ARG. Use diredp-recent-dirs. Pass SWITCHES. +;; dired-read-dir-and-switches: Use diredp-root-directory-p. +;; Bound diredp-dired-recent-dirs(-other-window) to C-x R and C-x 4 R. +;; Added diredp-dired-recent-dirs to Dir menu. +;; 2014/09/27 dadams +;; Added: diredp-dired-recent-dirs, diredp-dired-recent-dirs-other-window, diredp-delete-dups. +;; 2014/09/26 dadams +;; diredp-mouseover-help: dired-get-filename etc. has to be inside the save-excursion. +;; diredp-image-dired-create-thumb: Added FILE arg. Use numeric prefix arg as the new thumbnail size. +;; 2014/09/22 dadams +;; diredp-mouse-3-menu: Do not place overlay unless on a file/dir name (i.e., dired-get-filename). +;; 2014/09/15 dadams +;; dired-read-dir-and-switches: Made it (thus dired too) an Icicles multi-command. +;; dired (defadvice): Added doc about using it with Icicles. +;; 2014/09/14 dadams +;; Added: diredp-kill-this-tree. +;; Removed: diredp-dired-files(-other-window), diredp-dired-files-interactive-spec. +;; dired-read-dir-and-switches: +;; Based on diredp-dired-files-interactive-spec implementation now, but: +;; Moved unwind-protect outside call to list. completing-read, not read-string, for DIRBUF. +;; Do not allow inclusion of root directories. Protected icicle-sort-comparer with boundp. +;; dired-insert-subdir-validate: Make it a no-op. +;; dired advice (doc string): Mention wildcards, Icicles. +;; diredp-dired-for-files(-other-window): +;; Use dired-read-dir-and-switches and dired, not diredp-dired-files-interactive-spec and +;; diredp-dired-files. +;; diredp-menu-bar-immediate-menu, diredp-mouse-3-menu: +;; Added item for diredp-kill-this-tree. +;; Corrected visible condition: expand-file-name, so ~/ compares with its expansion. +;; diredp-font-lock-keywords-1: Include period (.) for diredp(-compressed)-file-suffix. +;; 2014/09/09 dadams +;; Added: dired-read-dir-and-switches. +;; Advise dired, for doc string. +;; dired-get-filename: Hack for Emacs 20-22, to expand ~/... +;; 2014/09/07 dadams +;; Added: redefinitions of dired-insert-subdir-newpos, dired-insert-subdir-validate. +;; 2014/07/26 dadams +;; diredp-do-find-marked-files-recursive: +;; Only ARG >= 0 ignores marks now. And ARG <= 0 means find but do not display. +;; 2014/07/13 dadams +;; diredp-mouseover-help: Wrap (goto-char pos) in save-excursion (Emacs bug #18011). +;; 2014/07/12 dadams +;; Faces diredp(-tagged)-autofile-name: Made paler/darker (less saturated). +;; Moved diredp-highlight-autofiles before diredp-highlight-autofiles-mode, so will be +;; defined for first revert. +;; diredp-mouse-3-menu: Renamed items Tag, Untag to Add Tags, Remove Tags. +;; diredp-dired-plus-description: Updated. +;; 2014/07/11 dadams +;; Added: diredp-highlight-autofiles-mode, diredp-highlight-autofiles, +;; diredp-autofile-name, diredp-tagged-autofile-name. +;; Soft-require bookmark+.el. Soft-require highlight.el if bookmark+.el is provided. +;; diredp-menu-bar-subdir-menu: Added item Toggle Autofile Highlighting. +;; Removed unused face: diredp-display-msg. +;; 2014/06/29 dadams +;; dired-get-marked-files, diredp-internal-do-deletions: +;; Remove nils from dired-map-over-marks result. +;; 2014/05/28 dadams +;; diredp-mode-line-marked: Use DarkViolet for both light and dark background modes. +;; 2014/05/23 dadams +;; Added: diredp-with-help-window. +;; diredp-list-files, diredp-dired-plus-help: +;; Use diredp-with-help-window, not with-output-to-temp-buffer. See Emacs bug #17109. +;; 2014/05/06 dadams +;; Added: diredp-image-dired-required-msg, diredp-list-files-map, +;; diredp-find-line-file-other-window, diredp-mouse-find-line-file-other-window, +;; image-dired-dired-toggle-marked-thumbs, diredp-list-marked. +;; Soft-require image-dired.el and image-file.el. +;; diredp-image-dired-create-thumb: Define unconditionally. +;; image-dired-dired-insert-marked-thumbs, diredp-image-dired-comment-file, +;; diredp-image-dired-tag-file, diredp-image-dired-delete-tag, +;; diredp-image-dired-display-thumb, diredp-image-dired-copy-with-exif-name, +;; diredp-image-dired-edit-comment-and-tags, diredp-do-display-images: +;; Define unconditionally and raise error if no image-(dired|file).el. +;; diredp-menu-bar-immediate-image-menu, diredp-menu-bar-images-menu, +;; diredp-menu-bar-images-recursive-menu, image-dired-mark-tagged-files: +;; Define unconditionally and use :enable. +;; diredp-menu-bar-images-menu, diredp-menu-bar-images-recursive-menu: +;; Add defalias so can use menu-item with :enable. +;; diredp-list-files: Add properties mouse-face, keymap, and help-echo. +;; diredp-mouseover-help: Make it work also for diredp-list-files listings. +;; image-dired-dired-insert-marked-thumbs: Add autoload cookie. +;; dired-get-marked-files: Pass non-nil 2nd arg to dired-get-filename, to include . and .. . +;; Bind diredp-list-marked to C-M-l and diredp-list-marked-recursive to M+ C-M-l. +;; diredp-insert-subdirs: Exclude . and .., as dired-get-marked-files can now include them. +;; diredp-menu-bar-operate-menu: Add diredp-menu-bar-operate-menu to menu. +;; diredp-dired-plus-description: Mention diredp-list-marked*. +;; 2014/05/03 dadams +;; dired-switches-escape-p: Use dired-switches-check if available, based on bug #17218 fix. +;; 2014/04/25 dadams +;; diredp-image-dired-create-thumb: +;; Do not call diredp-image-dired-create-thumb twice: reuse THUMB-NAME. +;; 2014/04/24 dadams +;; Added: diredp-mouseover-help, diredp-auto-focus-frame-for-thumbnail-tooltip-flag, +;; diredp-image-preview-in-tooltip. +;; dired-insert-set-properties: Show image-file preview in tooltip. +;; diredp-image-dired-create-thumb: Return thumbnail file name or nil. +;; 2014/04/23 dadams +;; Added: diredp-looking-at-p. +;; dired-insert-set-properties: Applied fix for bug #17228. +;; 2014/04/05 dadams +;; Added: diredp-do-bookmark-dirs-recursive. +;; Renamed from bmkp-create-dired-bookmarks-recursive in bookmark+-1.el (removed). +;; Bound to M-B (aka M-S-b). +;; Added to menus *-subdir-menu, *-operate-bookmarks-menu, *-bookmarks-menu. +;; diredp-get-confirmation-recursive: Added optional TYPE arg. +;; diredp-insert-subdirs-recursive: Call diredp-get-confirmation-recursive with TYPE arg. +;; 2014/02/16 dadams +;; dired-pop-to-buffer: Do not redefine for Emacs > 24.1. +;; dired-mark-pop-up: Updated doc string. +;; 2014/02/13 dadams +;; Added: diredp-fileset-other-window, diredp-fileset-1. +;; diredp-fileset: Use diredp-fileset-1. +;; Bind diredp-dired-union(-other-window) to C-x D, C-x 4 D, +;; diredp-fileset(-other-window) to C-x F, C-x 4 F. +;; Use diredp-fileset-other-window, not diredp-fileset, in menu. +;; 2014/02/03 dadams +;; Added: diredp-hide-subdir-nomove. +;; Added: dired-goto-file for Emacs 24+ - open hidden parent dir, so can goto destination. +;; Replace bindings for dired-hide-subdir with diredp-hide-subdir-nomove. +;; Bind dired-hide-subdir to M-$ (not $). +;; 2014/02/02 dadams +;; dired-goto-file: Redefine only for Emacs < 24. +;; 2014/01/15 dadams +;; Bind diredp-toggle-find-file-reuse-dir to C-M-R (aka C-M-S-r). +;; 2014/01/05 dadams +;; Bind dired-omit-mode (aka dired-omit-toggle) to C-x M-o. +;; 2013/12/05 dadams +;; diredp-do-grep-1: Call grep-default-command with arg, if grep+.el is loaded. +;; 2013/11/05 dadams +;; Added: diredp-get-subdirs. +;; diredp-get-files, diredp-get-files-for-dir, diredp-marked-here: Added optional arg NIL-IF-NONE-P. +;; diredp-get-files: Pass INCLUDE-DIRS-P to diredp-files-within. +;; 2013/11/04 dadams +;; Renamed Bookmarks submenus to Bookmark. +;; Added Bookmark Dired Buffer to Dir menu. +;; Alias dired-toggle-marks to dired-do-toggle for Emacs 20, instead of backwards for others. +;; Use dired-toggle-marks everywhere instead of dired-do-toggle. +;; 2013/11/03 dadams +;; Created submenus of Multiple menu: Bookmarks, Search. +;; Created submenus of Multiple > Marked Here and Below menu: +;; Images, Encryption, Search, Bookmarks. +;; Reordered menus. +;; 2013/09/26 dadams +;; diredp-next-line: Use let*, so line-move sees let bindings. +;; 2013/08/11 dadams +;; diredp-dired-files-interactive-spec: +;; Protect icicle-file-sort with boundp. Thx to Vladimir Lomov. +;; 2013/08/06 dadams +;; diredp-display-image,diredp-menu-bar-immediate-image-menu (:enable's): +;; Protect diredp-string-match-p from nil argument. +;; 2013/07/24 dadams +;; Added: diredp-nonempty-region-p. Use everywhere, in place of its definition. +;; 2013/07/21 dadams +;; Added: diredp-image-dired-(comment-file|copy-with-exif-name|(create|display)-thumb| +;; delete-tag|edit-comment-and-tags|tag-file), +;; diredp-string-match-p, diredp-menu-bar-immediate-image-menu. +;; Put this-file image commands on new menu diredp-menu-bar-immediate-image-menu. +;; diredp-menu-bar-images-menu: Added diredp-do-display-images. +;; Use diredp-string-match-p instead of string-match where appropriate. +;; diredp-find-a-file-read-args: Removed #' from lambda. +;; 2013/07/19 dadams +;; Added redefinition of dired-hide-details-mode. +;; Added: diredp-hide-details-propagate-flag, diredp-hide-details-initially-flag, +;; diredp-hide-details-last-state, diredp-hide-details-toggled, +;; diredp-hide-details-if-dired, global-dired-hide-details-mode, +;; diredp-fit-frame-unless-buffer-narrowed, diredp-hide/show-details, +;; diredp-do-display-images, diredp-display-image. +;; On dired-after-readin-hook: diredp-hide/show-details. +;; On dired-hide-details-mode-hook: diredp-fit-frame-unless-buffer-narrowed. +;; diredp-maplist: Use diredp-maplist, not maplist, in recursive call. +;; diredp-next-line: Added bobp test for negative ARG. +;; Emacs 20 line-move returns nil, so use (progn ... t). +;; Soft-require autofit-frame.el. +;; 2013/07/18 dadams +;; diredp-next-line: Protect visible-p with fboundp for Emacs 20. +;; 2013/07/17 dadams +;; Added: diredp-menu-bar-encryption-menu, diredp-menu-bar-images-menu, +;; diredp-menu-bar-immediate-encryption-menu, +;; diredp-(decrypt|verify|sign|encrypt)-this-file. +;; Added diredp-(decrypt|verify|sign|encrypt)-this-file to *-immediate-encryption-menu. +;; Moved encryption and image-dired items to the new Multiple submenus from Multiple menu. +;; 2013/07/15 dadams +;; Added: diredp-async-shell-command-this-file, diredp-do-async-shell-command-recursive. +;; Added them to menus. Bind diredp-do-async-shell-command-recursive to M-+ &. +;; diredp-menu-bar-mark-menu, diredp-dired-plus-description: Added dired-mark-omitted. +;; diredp-menu-bar-subdir-menu: Added dired-omit-mode, dired-hide-details-mode. +;; diredp-menu-bar-regexp-menu: Added image-dired-mark-tagged-files. +;; diredp-menu-bar-subdir-menu: Added dired-hide-details-mode. +;; diredp-shell-command-this-file: Corrected: provide file list to dired-do-shell-command. +;; 2013/07/13 dadams +;; diredp-font-lock-keywords-1: +;; Ensure diredp-dir-priv is not used for directory header of d:/... (Windows drive name). +;; dired-insert-directory: +;; Update wrt Emacs 24.4: Do dired-insert-set-properties last; use saved CONTENT-POINT. +;; dired-insert-set-properties: Updated for Emacs 24.4, for dired-hide-details-mode. +;; Add frame-fitting to dired-hide-details-mode-hook. +;; dired-mouse-find-file(-other-window): Error msg if click off a file name. +;; 2013/07/12 dadams +;; Added: diredp-wrap-around-flag, diredp-(next|previous)-(subdir|(dir)line). +;; Renamed dired-up-directory to diredp-up-directory. +;; Replaced vanilla commands by these new commands everywhere. +;; 2013/07/11 dadams +;; Added: diredp-up-directory-reuse-dir-buffer. +;; diredp-make-find-file-keys(-not)-reuse-dirs: Added diredp-up-directory-reuse-dir-buffer. +;; 2013/02/06 dadams +;; dired-mark-pop-up: goto point-min, so show start of file list. Thx to Michael Heerdegen. +;; 2013/01/28 dadams +;; Added redefinition of dired-do-run-mail. Fixes Emacs bug #13561. +;; 2012/12/18 dadams +;; diredp-ediff: Better default for FILE2. Thx to Michael Heerdegen. +;; Require subr-21.el for Emacs 20. +;; 2012/11/17 dadams +;; Added: derived-mode-p (for Emacs < 22), diredp-ensure-mode. +;; Use diredp-ensure-mode everywhere for mode, so compatible with Sunrise Commander etc. +;; 2012/11/01 dadams +;; Do not require ediff.el. It is required in diredp-ediff itself. +;; 2012/10/06 dadams +;; Added: minibuffer-with-setup-hook for code byte-compiled using Emacs < 22. +;; 2012/09/28 dadams +;; Moved dired-*w32* bindings after normal mouse bindings, so they override them. +;; 2012/09/05 dadams +;; diredp-(rename|copy|(rel)symlink|hardlink)-this-file: Bind use-file-dialog to nil. +;; 2012/08/26 dadams +;; Set font-lock-defaults to a 3-element list, so it works with font-menus(-da).el. +;; 2012/08/25 dadams +;; Added: redefinition of dired-pop-to-buffer (fix for bug #12281). +;; dired-mark-pop-up: If buffer is shown in a separate frame, do not show menu bar. +;; 2012/07/10 dadams +;; Removed unneeded substitute-key-definition for (next|previous)-line. +;; 2012/07/09 dadams +;; Added redefinition of dired-mark-files-regexp: Push REGEXP onto regexp-search-ring. +;; 2012/06/21 dadams +;; diredp-nb-marked-in-mode-name: +;; Add marker numbers regardless of name match. +;; Use text property dired+-mode-name to tell whether mode-name was already changed. +;; 2012/06/20 dadams +;; Added: diredp-nb-marked-in-mode-name, diredp-mode-line-(flagged|marked). Added to hooks. +;; Thx to Michael Heerdegen. +;; 2012/06/14 dadams +;; dired-mark-pop-up: Wrap save-excursion around window/frame deletion. +;; dired-do-redisplay: Updated wrt Emacs 23: bind, (then run) dired-after-readin-hook. +;; diredp-y-or-n-files-p: Corrected construction of prompt wrt final SPC. +;; 2012/06/13 dadams +;; dired-buffers-for-dir: Updated wrt Emacs 23: +;; If dired-directory is a list then expand FILE in DIR & check whether in cdr of list. +;; diredp-get-files-for-dir, diredp-files-within-1, diredp-insert-as-subdir: +;; Expand dir name before passing it to dired-buffers-for-dir. +;; 2012/06/05 dadams +;; MS Windows: Just do not define commands that are inappropriate for Windows (instead of +;; defining them to raise an error or making them invisible in menus). +;; 2012/06/02 dadams +;; Added: diredp-do-(print|encrypt|decrypt|sign|verify)-recursive. Menus. Keys. +;; diredp-do-move-recursive: Corrected to use dired-rename-file, not dired-copy-file. +;; 2012/05/30 dadams +;; diredp-insert-as-subdir: Added optional arg IN-DIRED-NOW-P. Pick up markings & switches +;; from sole Dired buffer for CHILD if not in Dired now. +;; 2012/05/29 dadams +;; Added: diredp-do-(chxxx|chgrp|chown|touch)-recursive, diredp-touch-this-file, +;; diredp-menu-bar-(immediate|operate)-bookmarks-menu. Added to menus. Bound to keys. +;; Factored bookmark stuff into Bookmark(s) submenus. +;; diredp-menu-bar-immediate-menu: Added dired-kill-subdir, [goto-subdir]. +;; diredp-dired-this-subdir, dired-maybe-insert-subdir: Corrected :visible/enable. +;; diredp-dired-inserted-subdirs: Do dired-(remember-marks|mark-remembered) in this-buff. +;; diredp-mouse-3-menu: +;; Do not use save-excursion, because some commands move point on purpose. Just return to +;; original point unless command intends to MOVEP. +;; Added to menu dired-maybe-insert-subdir (two entries), dired-kill-subdir. +;; Use *-this-file*, not *-do-*: copy|symlink|shell-command|grep|load (don't use :keys). +;; 2012/05/26 dadams +;; diredp-dired-inserted-subdirs, diredp-insert-as-subdir: +;; Preserve markings and switches in target buffer. +;; dired-mark-pop-up: Use unwind-protect. Bury buffer too. +;; diredp-do-chmod-recursive: Use only 5 args if < Emacs 23. +;; 2012/05/25 dadams +;; Added: diredp-insert-as-subdir, diredp-ancestor-dirs, diredp-maplist, +;; diredp-do-redisplay-recursive, diredp-do-chmod-recursive. +;; Bound diredp-do-chmod-recursive. to M-+ M and added to menu. +;; diredp-get-files: Added optional arg DONT-ASKP. +;; diredp-y-or-n-files-p: Kill list buffer if it was never shown. +;; dired-mark-pop-up: ignore error when delete frame/window. +;; 2012/05/22 dadams +;; diredp-get-files(-for-dir): Added optional arg INCLUDE-DIRS-P. +;; Added: diredp-insert-subdirs(-recursive), diredp(-this)-dired-inserted-subdir(s). +;; Added to menus. Bound diredp-insert-subdirs* to (M-+) M-i. +;; Bound diredp-capitalize(-recursive) to (M-+) %c. +;; Added diredp-dired-union-other-window to Dirs menu. +;; Updated diredp-dired-plus-description. +;; 2012/05/19 dadams +;; Added: diredp-image-dired-*-recursive, diredp-*link-recursive, +;; diredp-do-isearch(-regexp)-recursive, diredp-do-query-replace-regexp-recursive, +;; diredp-do-search-recursive, diredp-(capitalize|(up|down)case)-recursive, +;; diredp-create-files-non-directory-recursive. +;; Bound on M-+ prefix key. Added to menus. +;; diredp-get-files, diredp-y-or-n-files-p, diredp-list-files, diredp-list-marked-recursive: +;; Added optional arg PREDICATE. +;; diredp-do-create-files-recursive: Removed MARKER-CHAR arg. Hard-code to keep markings. +;; diredp-do-(copy|move)-recursive: Use arg IGNORE-MARKS-P (forgot to use it). +;; Removed MARKER-CHAR arg in call to d-d-c-f-r. +;; Added missing autoload cookies. +;; 2012/05/06 dadsms +;; diredp-y-or-n-files-p: Do not kill buffer *Files* - just bury it. +;; 2012/05/05 dadams +;; Added: diredp-do-bookmark-recursive, diredp-do-bookmark-in-bookmark-file-recursive, +;; diredp-set-bookmark-file-bookmark-for-marked-recursive. +;; Bound to M-+ M-b, M-+ C-M-B (aka C-M-S-b), M-+ C-M-b, respectively. Added to menus. +;; diredp-bookmark: Added optional arg FILE. +;; diredp-do-bookmark-in-bookmark-file: Added optional arg FILES. +;; diredp-dired-plus-description: Updated. +;; diredp-get-confirmation-recursive: Raise error if not in Dired. +;; diredp-do-bookmark-recursive, diredp-marked-recursive(-other-window), +;; diredp-multiple-w32-browser-recursive: +;; Use diredp-get-confirmation-recursive. +;; 2012/05/04 dadams +;; Added: dired-mark-unmarked-files for Emacs < 24. +;; diredp-do-create-files-recursive: Corrected for Emacs < 24. +;; diredp-do-create-files-recursive, diredp-(un)mark-files-tagged-regexp, +;; diredp(-mouse)-do-(un)tag, diredp(-mouse)-do-remove-all-tags, +;; diredp(-mouse)-do-paste-(add|replace)-tags, diredp(-mouse)-do-set-tag-value, +;; diredp(-mouse)-do-bookmark(-in-bookmark-file), diredp-find-a-file-read-args, +;; diredp-mouse-do-shell-command: +;; Use lexical-let(*), to get closures for free vars in lambdas. +;; 2012/04/28 dadams +;; Added: +;; diredp-copy-filename-as-kill-recursive, diredp-do-copy-recursive, +;; diredp-do-find-marked-files-recursive, diredp-do-grep-recursive, +;; diredp-do-move-recursive, diredp-do-shell-command-recursive, +;; diredp-list-marked-recursive, diredp-marked-recursive(-other-window), +;; diredp-multiple-w32-browser-recursive, diredp-do-create-files-recursive, +;; diredp-get-confirmation-recursive, diredp-list-files, diredp-y-or-n-files-p, +;; diredp-menu-bar-recursive-marked-menu. +;; diredp-get-files: Use diredp-y-or-n-files-p, not y-or-n-p. +;; Commented out dired-readin-insert - see comment. +;; Moved bookmark menu items to submenu Bookmarks. +;; Added keys (with M-+ prefix) and menu items for new (*-recursive) commands. +;; Reordered w32-browser stuff in menus. +;; diredp-do-grep: Combined defs for diff Emacs versions - do version test at runtime. +;; 2012/04/25 dadams +;; dired-insert-directory: Updated per Emacs 24. +;; 2012/04/23 dadams +;; Added (moved here from Icicles, and renamed prefix): +;; diredp-re-no-dot, diredp-get-files, diredp-get-files-for-dir, diredp-files-within, +;; diredp-files-within-dirs-done. +;; 2012/04/05 dadams +;; Added redefinition of dired-mark-pop-up, to fix Emacs bug #7533. If they ever fix it +;; then remove this hack. +;; 2012/03/13 dadams +;; diredp-dired(-for)-files(-other-window): +;; Bind: icicle-sort-comparer, icicle-all-candidates-list-alt-action-fn. +;; Use icicle-(un)bind-file-candidate-keys. +;; diredp-dired-files-interactive-spec: Updated doc strings accordingly. +;; 2012/03/07 dadams +;; Added: dired-switches-escape-p. +;; dired-get-filename: Updated wrt Emacs 24: +;; whitespace quoting for bug #10469, filename quoting per Emacs 23.3+, +;; MS Windows conversion of \ to / per Emacs 23.3+. +;; dired-goto-file: Escape whitespace, per Emacs 24 (for bug #10469). +;; 2012/03/02 dadams +;; Require cl.el at compile time even for Emacs 22+, for case macro. +;; 2012/02/28 dadams +;; Do not bother to soft-require mkhtml.el anymore. +;; 2012/02/18 dadams +;; Swapped keys for dired-w32(-browser|explore), so the former is M-RET, as in Bookmark+. +;; 2012/01/10 dadams +;; diredp-font-lock-keywords-1: Corrected for date/time when locale is used, not iso. +;; 2011/12/19 dadams +;; dired-insert-set-properties, dired-mark-sexp, diredp-(un)mark-region-files, +;; diredp-flag-region-files-for-deletion, diredp-mouse-3-menu: +;; Use line-(beginning|end)-position. +;; 2011/12/16 dadams +;; diredp-menu-bar-mark-menu: Removed Revert item. +;; diredp-menu-bar-subdir-menu: Add image-dired-dired-toggle-marked-thumbs. +;; diredp-mouse-3-menu: +;; Use commands bound to keys, so the keys show up in the menu. Prefer *-this-file. +;; Correct the mark/unmark/flag menu-item visibility. Added Capitalize. +;; 2011/12/09 dadams +;; diredp-w32-drives: Use dolist, not mapcar. +;; diredp-mouse-3-menu: Use easymenu to build the menu. Conditionalize some items. +;; Bind down-mouse-3, not mouse-3, to diredp-mouse-3-menu. (bind mouse-3 to ignore). +;; Added eval-when-compile for easymenu.el. +;; 2011/12//02 dadams +;; Added diredp-internal-do-deletions. +;; dired(-mouse)-do(-flagged)-delete, : Use diredp-internal-do-deletions, for trash. +;; 2011/11/29 dadams +;; diredp-read-bookmark-file-args: Corrected use of list of default file names: > Emacs 23.1. +;; 2011/10/31 dadams +;; dired-mode-hook: Call font-lock-refresh-defaults - see Emacs 24 bugs #6662 and #9919. +;; 2011/10/24 dadams +;; Protect dired-show-file-type with fboundp. +;; 2011/09/03 dadams +;; diredp-do-grep-1: Map shell-quote-argument over file names. Thx to Joe Bloggs. +;; 2011/08/07 dadams +;; diredp-bookmark (need to keep in sync with bmkp-make-record-for-target-file): +;; Instead of image-bookmark-make-record, use explicit function that includes file, type. +;; 2011/07/25 dadams +;; Changed featurep to eval-after-load, for bookmark+-1.el and w32-browser.el. +;; 2011/07/01 dadams +;; Fixed typo: dired-toggle-find-file-reuse-dir -> ...diredp.... Thx to pasja on Emacs Wiki. +;; 2011/06/18 dadams +;; Added: diredp-describe-mode, diredp-dired-plus-help(-link), diredp-help-button, +;; diredp-dired-plus-description(+links), diredp-send-bug-report. +;; Bound diredp-describe-mode to whatever describe-mode is bound to. +;; All menus, :enable with mark-active: Added transient-mark-mode and mark != point. +;; toggle-diredp-find-file-reuse-dir: Swapped which one is the alias. +;; diredp-w32-list-mapped-drives: Display *Shell Command Output* at end. +;; diredp-mouse-(describe-file|3-menu|mark/unmark|(find|view)-file(-other-window)): +;; save-excursion set-buffer -> with-current-buffer. +;; 2011/06/08 dadams +;; Added: diredp-dired-for-files(-other-window). +;; 2011/06/07 dadams +;; Bound dired-show-file-type to _, since y is diredp-relsymlink-this-file. +;; 2011/04/25 dadams +;; Added (from files+.el): dired(-mouse)-describe-file. Bound to C-h (C-)RET, added to menus. +;; 2011/04/23 dadams +;; Added, bound (T c, T M-w, T 0, T v, T p, T C-y, T q), and added to menus: +;; diredp-copy-tags-this-file, diredp-mouse-copy-tags, +;; diredp(-mouse)(-do)-remove-all-tags(-this-file), +;; diredp(-mouse)(-do)-set-tag-value(-this-file), +;; diredp(-mouse)(-do)-paste-(add|replace)-tags(-this-file). +;; diredp-mark-files-tagged-(all/none|some/not-all): Bound free var presentp. +;; dired-map-over-marks: Corrected: Bind NEWARG and use that, not ARG. +;; dired-get-marked-files: let* -> let. +;; dired-do-redisplay, diredp-mouse-diff: when/if -> and. +;; dired-readin-insert, dired-get-filename: if -> unless/when. +;; diredp-mouse-find-file-reuse-dir-buffer: with-current-buffer, not save... +;; diredp-mouse-mark/unmark: Removed unused bol/eol vars. +;; 2011/04/19 dadams +;; Added: diredp-(un)mark-files-tagged-((not-)all|none|some|regexp|all/none|some/not-all), +;; dired-mark-if. Added Tagged submenu for Mark menu. +;; Put tags commands on prefix key T, as in Bookmark+. Removed C-(M-)+/- tags-cmd bindings. +;; diredp-untag-this-file: Added prefix-arg behavior. +;; 2011/04/18 dadams +;; Added: diredp-prompt-for-bookmark-prefix-flag. +;; Use it in diredp(-mouse)-do-(un)tag, diredp-read-bookmark-file-args, +;; diredp(-mouse)-do-bookmark, diredp-(bookmark|(un)tag)-this-file. +;; diredp-(bookmark|(un)tag)-this-file, diredp(-do)-bookmark, diredp-(un)tag, +;; diredp-do-bookmark-in-bookmark-file, diredp-set-bookmark-file-bookmark-for-marked: +;; Made PREFIX arg optional. +;; 2011/04/17 dadams +;; Added: diredp-(bookmark|(un)tag)-this-file, diredp(-mouse)(-do)-(un)tag. +;; diredp-mouse-3-menu: Added: diredp-mouse-do-(un)tag. +;; diredp-menu-bar-immediate-menu: Added diredp-(un)tag-this-file, diredp-bookmark-this-file. +;; diredp-menu-bar-operate-menu: Added diredp-do-(un)tag. +;; Bound diredp-do-tag to C-+, diredp-tag-this-file to C-M-+, diredp-do-untag to C--, +;; diredp-untag-this-file to C-M--, diredp-bookmark-this-file to C-B. +;; diredp-bookmark: Use bmkp-autofile-set, not bmkp-file-target-set, so get autofile. +;; diredp-read-bookmark-file-args, diredp(-mouse)-do-bookmark: +;; Default for prefix is now an empty string, not the directory. +;; diredp-mouse-do-bookmark: Removed optional second arg. +;; Corrected typo: direp-read-bookmark-file-args -> diredp-read-bookmark-file-args. +;; 2011/03/25 dadams +;; diredp-bookmark: Fixed typo: bmkp-file-indirect-set -> bmkp-file-target-set. +;; 2011/02/11 dadams +;; diredp-deletion, diredp-deletion-file-name, diredp-executable-tag: +;; Made default the same for dark background as for light. +;; diredp-ignored-file-name: Made default a bit darker for dark background. +;; 2011/02/03 dadams +;; All deffaces: Provided default values for dark-background screens too. +;; 2011/01/12 dadams +;; dired-do-flagged-delete: Removed sit-for added on 1/02. +;; 2011/01/04 dadams +;; defsubst -> defun everywhere. +;; Removed autoload cookies from non def* sexps, defvar, and non-interactive functions. +;; Added some missing autoload cookies for defcustom and commands. +;; 2011/01/02 dadams +;; Added: diredp-this-file-(un)marked-p, diredp-toggle-marks-in-region. +;; diredp-(un)mark-region-files, diredp-flag-region-files-for-deletion: +;; Act only on marked/unmarked files (opposite). Fix 2nd arg to dired-mark-if. +;; diredp-mouse-3-menu: +;; If region is active and mouse3.el was loaded, then use its popup. +;; Fix Toggle Marked/Unmarked: +;; Use diredp-toggle-marks-in-region, so widen, show details and use bol/eol. +;; dired-do-flagged-delete: Added sit-for. +;; 2010/11/28 dadams +;; diredp-mouse-3-menu: Added Toggle Marked/Unmarked for region menu. +;; 2010/10/20 dadams +;; Moved Emacs 20 tweak to recognize k in file sizes to var dired-move-to-filename-regexp. +;; Added diredp-loaded-p. +;; 2010/10/19 dadams +;; diredp-font-lock-keywords-1: +;; Handle decimal pt in file size. Thx to Michael Heerdegen. +;; Enable Emacs 20/21 to handle -h option (decimal point size). +;; Renamed: face diredp-inode+size to diredp-number. +;; 2010/10/01 dadams +;; dired-goto-file: Avoid infloop from looking for dir line. Thx to not-use.dilines.net. +;; 2010/09/29 dadams +;; Added: diredp-dired-union(-1|-other-window|-interactive-spec). +;; dired-goto-file: fix for Emacs bug #7126. +;; 2010/09/27 dadams +;; Renamed diredp-dired-interactive-spec to diredp-dired-files-interactive-spec. +;; diredp-dired-files-interactive-spec: Respect file-list arg: kill existing Dired buffer. +;; Fix use of prefix arg for switches. +;; 2010/09/26 dadams +;; Added: dired-insert-directory: Compute WILDCARD arg for individual files. +;; Added: dired-readin-insert: Use t as WILDCARD arg to dired-insert-directory. +;; Added: diredp-dired-files(-other-window), diredp-dired-interactive-spec. +;; 2010/08/27 dadams +;; Use diredp-font-lock-keywords-1 properly as a second level of fontification. +;; Added: diredp-w32-drives(-mode(-map)), dired-up-directory. +;; 2010/08/07 dadams +;; dired-map-over-marks: Removed loop that used dired-between-files. +;; diredp-get-file-or-dir-name: test against subdir/..? also. +;; dired-do-find-marked-files: Pass original ARG to dired-get-marked-files. +;; 2010/08/05 dadams +;; diredp-bookmark: +;; Handle image files (and sound files, if Bookmark+ is used). +;; Use bmkp-file-indirect-set if available. +;; Use error-message-string to get failure msg. +;; 2010/07/11 dadams +;; Added: diredp-set-bookmark-file-bookmark-for-marked (C-M-b), diredp-mouse-do-bookmark, +;; diredp-do-bookmark-in-bookmark-file (C-M-B, aka C-M-S-b), diredp-read-bookmark-file-args. +;; Added them to the operate menu. Added diredp-do-bookmark to mouse-3 menu. +;; 2010/07/07 dadams +;; dired-do-*: Updated doc strings for prefix arg treatment from dired-map-over-marks-check. +;; Added missing autoload cookies. +;; 2010/05/29 dadams +;; diredp-bookmark: Use relative file name in bookmark name. +;; Removed defvar of directory-listing-before-filename-regexp. +;; 2010/05/28 dadams +;; Changed menu item for dired-create-directory to New Directory. Moved it before Up Dir. +;; 2010/03/19 dadams +;; diredp-font-lock-keywords-1: Handle date+time wrt regexp changes for Emacs 23.2. +;; 2010/01/31 dadams +;; diredp-bookmark: +;; Don't use bookmark-set or find-file-noselect - inline the needed bookmark-store code. +;; Call bookmark-maybe-load-default-file. Use rudimentary bookmark-make-record-function. +;; 2010/01/21 dadams +;; Renamed: +;; diredp-subst-find-alternate-for-find to diredp-make-find-file-keys-reuse-dirs +;; diredp-subst-find-for-find-alternate to diredp-make-find-file-keys-not-reuse-dirs. +;; diredp-make-find-file-keys(-not)-reuse-dirs: Handle also dired(-mouse)-w32-browser. +;; 2010/01/10 dadams +;; Added: face diredp-inode+size. Use in diredp-font-lock-keywords-1. +;; diredp-font-lock-keywords-1: Allow decimal point in file size. Thx to Regis. +;; 2010/01/05 dadams +;; dired-insert-set-properties: +;; Add text property dired-filename to the file name (for Emacs 23). +;; 2009/10/23 dadams +;; diredp-font-lock-keywords-1: Override `l' and `t' matches in headings with default face. +;; 2009/10/13 dadams +;; Added: diredp(-do)-bookmark. Added to Multiple menu, and bound to M-b. +;; 2009/10/11 dadams +;; diredp-menu-bar-immediate-menu: +;; Added items: image display items, dired-maybe-insert-subdir. +;; Test dired-do-relsymlink, not diredp-relsymlink-this-file. +;; diredp-menu-bar-operate-menu: +;; Added items: epa encryption items, image items, isearch items. +;; diredp-menu-bar-subdir-menu: +;; Added items: revert, isearch file names, dired-compare-directories. +;; Removed macro menu-item-any-version - use menu-item everywhere (works for Emacs 20+). +;; Added wdired-change-to-wdired-mode to subdir menu even for Emacs 20, if defined. +;; 2009/07/09 dadams +;; dired-goto-file: Make sure we have a string before calling directory-file-name. +;; 2009/05/08 dadams +;; dired-find-file (Emacs 20): Raise error if dired-get-filename returns nil. +;; 2009/04/26 dadams +;; dired-insert-set-properties, diredp-(un)mark-region-files, +;; diredp-flag-region-files-for-deletion, diredp-mouse-3-menu, diredp-mouse-mark/unmark: +;; Bind inhibit-field-text-motion to t, to ensure real eol. +;; 2008/12/17 dadams +;; diredp-font-lock-keywords-1: Don't do diredp-deletion, diredp-flag-mark for empty lines. +;; 2008/09/22 dadams +;; Added: diredp-fileset, diredp-get-file-or-dir-name, and redefinitions of +;; dired-map-over-marks, dired-find-file, and dired-mouse-find-file-other-window. +;; Added vanilla code to pick up macro dired-map-over-marks: +;; dired-get-marked-files, dired-do-delete, dired-map-over-marks-check, +;; dired-do-redisplay, image-dired-dired-insert-marked-thumbs. +;; diredp-find-file-other-frame, diredp-mouse-(find|view)-file: +;; Added nil t args to dired-get-filename calls. +;; diredp-do-grep(-1): Use new dired-get-marked-files instead of ad-hoc treatment of C-u. +;; 2008/09/21 dadams +;; diredp-marked(-other-window): Don't treat zero prefix arg as numerical (no empty Dired). +;; Added dired-find-file redefinition for Emacs 20. +;; 2008/09/11 dadams +;; diredp-do-grep: Plain C-u means grep all files in Dired buffer. +;; diredp-do-grep-1: Treat 'all value of FILES arg. +;; Added: diredp-all-files. +;; 2008/09/09 dadams +;; Added: diredp-marked(-other-window). Added to menus. Bound *-other-window to C-M-*. +;; 2008/09/07 dadams +;; Added: diredp(-mouse)-do-grep(-1), diredp-grep-this-file. +;; Bound diredp-do-grep to M-g. Added grep commands to menus. +;; 2008/07/18 dadams +;; Soft-require w32-browser.el. Bind its commands in Dired map and menus. +;; 2008/03/08 dadams +;; dired-maybe-insert-subdir: Fit one-window frame after inserting subdir. +;; 2008/03/07 dadams +;; Added: redefinitions of dired-maybe-insert-subdir, dired-goto-file, dired-get-filename. +;; Added: diredp-this-subdir. +;; 2007/11/27 dadams +;; diredp-mouse(-backup)-diff: If available, use icicle-read-string-completing. +;; 2007/09/23 dadams +;; Removed second arg to undefine-killer-commands. +;; 2007/07/27 dadams +;; diredp-font-lock-keywords-1: Allow also for bz2 compressed files - Thx to Andreas Eder. +;; 2006/09/03 dadams +;; diredp-font-lock-keywords-1: Corrected file size and inode number. Thx to Peter Barabas. +;; 2006/08/20 dadams +;; Added: diredp-find-a-file*. +;; 2006/06/18 dadams +;; diredp-font-lock-keywords-1: Highlight file name (also) of flagged files. +;; Use dired-del-marker instead of literal D. +;; Added: diredp-deletion-file-name. +;; 2006/03/31 dadams +;; No longer use display-in-minibuffer. +;; 2006/01/07 dadams +;; Added: link for sending bug report. +;; 2006/01/06 dadams +;; Added defgroup Dired-Plus and used it. Added :link. +;; 2006/01/04 dadams +;; Added defvar of directory-listing-before-filename-regexp, for Emacs 22 compatibility. +;; 2005/12/29 dadams +;; Added: diredp-mouse-mark/unmark-mark-region-files. +;; 2005/12/26 dadams +;; Updated groups. +;; 2005/12/05 dadams +;; diredp-ignored-file-name: Made it slightly darker. +;; 2005/11/05 dadams +;; Renamed all stuff defined here to have diredp- prefix. +;; diredp-relsymlink-this-file: Protected with fboundp. +;; Changed to soft require: dired-x.el. +;; Removed comment to require this inside eval-after-load. +;; 2005/11/03 dadams +;; Added: dired-display-msg. Replace blue-foreground-face with it. +;; Alias dired-do-toggle to dired-toggle-marks, if defined. +;; 2005/11/02 dadams +;; Added: dired-get-file-for-visit, dired(-mouse)-find-alternate-file*, +;; togglep-dired-find-file-reuse-dir, dired+-subst-find-*. +;; Use defface for all faces. Renamed without "-face". No longer require def-face-const. +;; dired-simultaneous-find-file: Minor bug fix (typo). +;; 2005/07/10 dadams +;; dired-unmark-all-files-no-query -> dired-unmark-all-marks +;; (thanks to Sivaram Neelakantan for bug report). +;; 2005/05/25 dadams +;; string-to-int -> string-to-number everywhere. +;; 2005/05/17 dadams +;; Updated to work with Emacs 22.x. +;; 2005/02/16 dadams +;; Added dired-mark/unmark-extension. Replaced dired-mark-extension with it everywhere. +;; 2005/01/08 dadams +;; Bind [S-mouse-1], instead of [S-down-mouse-1], to dired-mouse-mark-region-files. +;; 2004/11/20 dadams +;; dired-mark-sexp: Search for literal month names only for versions before Emacs 20. +;; Refined to deal with Emacs 21 < 21.3.50 (soon to be 22.x) +;; 2004/11/14 dadams +;; Bound dired-no-confirm to non-nil for dired-mouse-*. +;; Updated for Emacs 21 and improved highlighting: +;; Spaces OK in file and directory names. Highlight date/time and size. +;; 2004/10/17 dadams +;; Require cl only for Emacs 20, and only when compile. +;; 2004/10/01 dadams +;; Updated to work with Emacs 21 also. +;; 2004/04/02 dadams +;; dired-font-lock-keywords-1: Prefer using dired-omit-extensions +;; to completion-ignored-extensions, if available. +;; 2004/03/22 dadams +;; Added dired-mouse-mark-region-files and dired-mouse-mark/unmark. +;; 2000/09/27 dadams +;; 1. dired-font-lock-keywords-1: fixed for spaces in dir names. +;; 2. Added: dired-buffers-for-dir. +;; 1999/09/06 dadams +;; Added S-*-mouse-2 bindings (same as C-*-mouse-2). +;; 1999/08/26 dadams +;; 1. Added *-face vars and dired-font-lock-keywords-1. +;; 2. Added possibility to use dired-font-lock-keywords-1 via hook. +;; 1999/08/26 dadams +;; Changed key binding of dired-mouse-find-file from down-mouse-2 to mouse-2. +;; 1999/08/25 dadams +;; Changed (C-)(M-)mouse-2 bindings. +;; 1999/08/25 dadams +;; 1. Added cmds & menu bar and key bindings: (dired-)find-file-other-frame. +;; 2. Changed binding for dired-display-file. +;; 1999/03/26 dadams +;; 1. Get rid of Edit menu-bar menu. +;; 2. dired-mouse-3-menu: Changed popup titles and item names. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Code: + +(eval-when-compile (require 'cl)) ;; case (plus, for Emacs 20: dolist, pop, push) +(eval-when-compile (require 'easymenu)) ;; easy-menu-create-menu + +(require 'dired) ;; dired-revert +(require 'dired-aux) ;; dired-bunch-files, dired-do-chxxx, dired-do-create-files, + ;; dired-mark-read-string, dired-read-shell-command, + ;; dired-run-shell-command, dired-shell-stuff-it +(require 'dired-x) ;; dired-do-relsymlink +(require 'autofit-frame nil t) ;; (no error if not found) fit-frame-if-one-window +(require 'bookmark+ nil t) ;; (no error if not found) + ;; bmkp-autofile-add-tags, bmkp-autofile-remove-tags, bmkp-autofile-set, bmkp-copied-tags, + ;; bmkp-current-bookmark-file, bmkp-describe-bookmark, bmkp-empty-file, bmkp-get-autofile-bookmark, + ;; bmkp-get-bookmark-in-alist, bmkp-get-tags, bmkp-read-tag-completing, + ;; bmkp-read-tags-completing, bmkp-refresh/rebuild-menu-list, bmkp-remove-all-tags, + ;; bmkp-same-file-p, bmkp-set-bookmark-file-bookmark, bmkp-set-sequence-bookmark, + ;; bmkp-set-tag-value, bmkp-some, bmkp-switch-bookmark-file, bmkp-tag-name + +;; For now at least, `highlight.el' is needed only if you use `bookmark+.el'. +(when (featurep 'bookmark+) (require 'highlight nil t)) ;; (no error if not found): + ;; hlt-highlight-region + +(if (> emacs-major-version 21) (require 'help-fns+ nil t) (require 'help+20 nil t)) ;; (no error if not found): + ;; describe-file + +(require 'misc-fns nil t) ;; (no error if not found): undefine-killer-commands +(require 'image-file nil t) ;; (no error if not found): image-file-name-regexp +(require 'image-dired nil t) ;; (no error if not found): + ;; image-dired-create-thumb, image-dired-create-thumbnail-buffer, + ;; image-dired-dired-after-readin-hook, image-dired-delete-tag, image-dired-dired-comment-files, + ;; image-dired-dired-display-external, image-dired-dired-display-image, + ;; image-dired-display-thumbs, image-dired-get-comment, image-dired-get-exif-file-name, + ;; image-dired-get-thumbnail-image, image-dired-insert-thumbnail, image-dired-line-up, + ;; image-dired-line-up-dynamic, image-dired-line-up-interactive, image-dired-line-up-method, + ;; image-dired-list-tags, image-dired-main-image-directory, image-dired-mark-tagged-files, + ;; image-dired-read-comment, image-dired-remove-tag, image-dired-save-information-from-widgets, + ;; image-dired-tag-files, image-dired-thumb-height, image-dired-thumbnail-buffer, + ;; image-dired-thumb-name, image-dired-thumb-size, image-dired-thumb-width, + ;; image-dired-widget-list, image-dired-write-comments, image-dired-write-tags +(when (memq system-type '(windows-nt ms-dos)) + ;; (no error if not found): + (require 'w32-browser nil t));; dired-w32explore, dired-w32-browser, dired-mouse-w32-browser, + ;; dired-multiple-w32-browser +(when (< emacs-major-version 21) (require 'subr-21)) ;; replace-regexp-in-string + +;; Provide macro for code byte-compiled using Emacs < 22. +(eval-when-compile + (when (< emacs-major-version 22) + (defmacro minibuffer-with-setup-hook (fun &rest body) + "Temporarily add FUN to `minibuffer-setup-hook' while executing BODY. +BODY should use the minibuffer at most once. +Recursive uses of the minibuffer are unaffected (FUN is not +called additional times). + +This macro actually adds an auxiliary function that calls FUN, +rather than FUN itself, to `minibuffer-setup-hook'." + ;; (declare (indent 1) (debug t)) + (let ((hook (make-symbol "setup-hook"))) + `(let (,hook) + (setq ,hook (lambda () + ;; Clear out this hook so it does not interfere + ;; with any recursive minibuffer usage. + (remove-hook 'minibuffer-setup-hook ,hook) + (funcall ,fun))) + (unwind-protect + (progn (add-hook 'minibuffer-setup-hook ,hook) ,@body) + (remove-hook 'minibuffer-setup-hook ,hook))))))) + +(defmacro diredp-user-error (&rest args) + `(if (fboundp 'user-error) (user-error ,@args) (error ,@args))) + +;; Define these for Emacs 20 and 21. +(unless (fboundp 'dired-get-file-for-visit) ; Emacs 22+ + (defun dired-get-file-for-visit () ; Not bound + "Get the current line's file name, with an error if file does not exist." + (interactive) + (let ((raw (dired-get-filename nil t)) ; Pass t for second arg so no error for `.' and `..'. + file-name) + (unless raw (error "No file on this line")) + (setq file-name (file-name-sans-versions raw t)) + (if (file-exists-p file-name) + file-name + (if (file-symlink-p file-name) + (error "File is a symlink to a nonexistent target") + (error "File no longer exists; type `g' to update Dired buffer"))))) + + (defun dired-find-alternate-file () ; Not bound + "In Dired, visit this file or directory instead of the Dired buffer." + (interactive) + (set-buffer-modified-p nil) + (find-alternate-file (dired-get-file-for-visit)))) + +;;;;;;;;;;;;;;;;;;;;;;; + + +(provide 'dired+) +(require 'dired+) ; Ensure loaded before compile this. + +;; Quiet the byte-compiler. +(defvar bmkp-copied-tags) ; In `bookmark+-1.el' +(defvar bmkp-current-bookmark-file) ; In `bookmark+-1.el' +(defvar bookmark-default-file) ; In `bookmark.el' +(defvar compilation-current-error) ; In `compile.el' +(defvar delete-by-moving-to-trash) ; Built-in, Emacs 23+ +(defvar dired-always-read-filesystem) ; In `dired.el', Emacs 26+ +(defvar dired-auto-revert-buffer) ; In `dired.el', Emacs 23+ +(defvar dired-create-files-failures) ; In `dired-aux.el', Emacs 22+ +(defvar dired-details-state) ; In `dired-details+.el' +(defvar dired-keep-marker-hardlink) ; In `dired-x.el' +(defvar dired-overwrite-confirmed) ; In `dired-aux.el' +(defvar dired-query-alist) ; In `dired-aux.el', Emacs < 24 +(defvar dired-recursive-copies) ; In `dired-aux.el', Emacs 22+ +(defvar dired-recursive-deletes) ; In `dired.el', Emacs 22+ +(defvar dired-shrink-to-fit) ; In `dired.el' +(defvar dired-switches-alist) ; In `dired.el' +(defvar dired-subdir-switches) ; In `dired.el' +(defvar dired-touch-program) ; Emacs 22+ +(defvar dired-use-ls-dired) ; Emacs 22+ +(defvar diredp-count-.-and-..-flag) ; Here, Emacs 22+ +(defvar diredp-hide-details-initially-flag) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-last-state) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-propagate-flag) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-toggled) ; Here, Emacs 24.4+ +(defvar diredp-highlight-autofiles-mode) ; Here, Emacs 22+ +(defvar diredp-menu-bar-encryption-menu) ; Here, Emacs 23+ +(defvar diredp-menu-bar-images-recursive-menu) ; Here (old name) +(defvar diredp-menu-bar-regexp-recursive-menu) ; Here (old name) +(defvar diredp-menu-bar-subdir-menu) ; Here (old name) +(defvar diredp-move-file-dirs) ; Here, Emacs 24+ +(defvar diredp-single-bookmarks-menu) ; Here, if Bookmark+ is available +(defvar filesets-data) ; In `filesets.el' +(defvar grep-use-null-device) ; In `grep.el' +(defvar header-line-format) ; Emacs 22+ +(defvar icicle-file-sort) ; In `icicles-opt.el' +;; $$$$ (defvar icicle-file-sort-first-time-p) ; In `icicles-var.el' +(defvar icicle-files-ido-like-flag) ; In `icicles-opt.el' +(defvar icicle-ignored-directories) ; In `icicles-opt.el' +(defvar icicle-sort-comparer) ; In `icicles-opt.el' +(defvar image-dired-display-image-buffer) ; In `image-dired.el' +(defvar image-dired-line-up-method) ; In `image-dired.el' +(defvar image-dired-main-image-directory) ; In `image-dired.el' +(defvar image-dired-thumbnail-buffer) ; In `image-dired.el' +(defvar image-dired-thumb-height) ; In `image-dired.el' +(defvar image-dired-thumb-width) ; In `image-dired.el' +(defvar image-dired-widget-list) ; In `image-dired.el' +(defvar ls-lisp-use-insert-directory-program) ; In `ls-lisp.el' +(defvar minibuffer-default-add-function) ; In `simple.el', Emacs 23+ +(defvar mouse3-dired-function) ; In `mouse3.el' +(defvar read-file-name-completion-ignore-case) ; In `minibuffer.el', Emacs 23+. In C code, Emacs 22. +(defvar recentf-list) ; In `recentf.el' +(defvar switch-to-buffer-preserve-window-point) ; In `window.el', Emacs 24+ +(defvar tooltip-mode) ; In `tooltip.el' +(defvar vc-directory-exclusion-list) ; In `vc' +(defvar w32-browser-wait-time) ; In `w32-browser.el' + +;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup Dired-Plus nil + "Various enhancements to Dired." + :prefix "diredp-" :group 'dired + :link `(url-link :tag "Send Bug Report" + ,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\ +dired+.el bug: \ +&body=Describe bug here, starting with `emacs -q'. \ +Don't forget to mention your Emacs and library versions.")) + :link '(url-link :tag "Other Libraries by Drew" + "https://www.emacswiki.org/emacs/DrewsElispLibraries") + :link '(url-link :tag "Download" + "https://www.emacswiki.org/emacs/download/dired%2b.el") + :link '(url-link :tag "Description" + "https://www.emacswiki.org/emacs/DiredPlus") + :link '(emacs-commentary-link :tag "Commentary" "dired+")) + +;;; Variables + +;; `dired-do-toggle' was renamed to `dired-toggle-marks' after Emacs 20. +(unless (fboundp 'dired-toggle-marks) (defalias 'dired-toggle-marks 'dired-do-toggle)) + +;;; This is duplicated in `diff.el' and `vc.el'. +;;;###autoload +(defcustom diff-switches "-c" + "*A string or list of strings specifying switches to be passed to diff." + :type '(choice string (repeat string)) + :group 'dired :group 'diff) + +;;;###autoload +(defcustom diredp-auto-focus-frame-for-thumbnail-tooltip-flag nil + "*Non-nil means automatically focus the frame for a thumbnail tooltip. +If nil then you will not see a thumbnail image tooltip when you +mouseover an image-file name in Dired, unless you first give the frame +the input focus (e.g., by clicking its title bar). + +This option has no effect if `diredp-image-preview-in-tooltip' is nil. +It also has no effect for Emacs versions prior to Emacs 22." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-bind-problematic-terminal-keys t + "*Non-nil means bind some keys that might not work in a text-only terminal. +This applies to keys that use modifiers Meta and Shift together. +If you use Emacs in text-only terminal and your terminal does not +support the use of such keys then customize this option to nil." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-compressed-extensions '(".tar" ".taz" ".tgz" ".arj" ".lzh" + ".lzma" ".xz" ".zip" ".z" ".Z" ".gz" ".bz2" ".rar" ".rev") + "*List of compressed-file extensions, for highlighting. + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :type '(repeat string) :group 'Dired-Plus) + +(when (> emacs-major-version 21) ; Emacs 22+ + (defcustom diredp-count-.-and-..-flag nil + "Non-nil means count `.' and `..' when counting files for mode-line." + :type 'boolean :group 'Dired-Plus)) + +;;;###autoload +(defcustom diredp-do-report-echo-limit 5 + "Echo result for each file, for fewer than this many files. +If more than this many files are acted on then there is no echoing. + +Used by some do-and-report commands such as `diredp-do-emacs-command'. +Results that are not echoed are anyway reported by `dired-log', so you +can show them with `?' in the Dired buffer." + :type '(restricted-sexp :match-alternatives (wholenump)) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-dwim-any-frame-flag pop-up-frames + "*Non-nil means the target directory can be in a window in another frame. +Only visible frames are considered. +This is used by ``dired-dwim-target-directory'. +This option has no effect for Emacs versions before Emacs 22." + :type 'boolean :group 'Dired-Plus) + +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (defcustom diredp-hide-details-initially-flag t + "*Non-nil means hide details in Dired from the outset." + :type 'boolean :group 'Dired-Plus + :set (lambda (sym defs) + (custom-set-default sym defs) + (setq diredp-hide-details-last-state diredp-hide-details-initially-flag))) + + (defcustom diredp-hide-details-propagate-flag t + "*Non-nil means display the next Dired buffer the same way as the last. +The last `dired-hide-details-mode' value set is used by the next Dired +buffer created." + :type 'boolean :group 'Dired-Plus)) + +;; Emacs 20 only. +;;;###autoload +(unless (fboundp 'define-minor-mode) + (defcustom diredp-highlight-autofiles-mode t + "*Non-nil means highlight names of files that are autofile bookmarks. +Autofiles that have tags are highlighted using face +`diredp-tagged-autofile-name'. Those with no tags are highlighted +using face `diredp-autofile-name'. + +Setting this option directly does not take effect; use either +\\[customize] or command `diredp-highlight-autofiles-mode'. + +NOTE: When `dired+.el' is loaded (for the first time per Emacs +session), the highlighting is turned ON, regardless of the option +value. To prevent this and have the highlighting OFF by default, you +must do one of the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize the option to `nil', AND ensure that your `custom-file' + (or the `custom-saved-variables' part of your init file) is + evaluated before `dired+.el' is loaded. + +This option has no effect unless you use libraries `Bookmark and +`highlight.el'." + :set (lambda (symbol value) (diredp-highlight-autofiles-mode (if value 1 -1))) + :initialize 'custom-initialize-default + :type 'boolean :group 'Dired-Plus :require 'dired+)) + +;;;###autoload +(defcustom diredp-ignore-compressed-flag t + "*Non-nil means to font-lock names of compressed files as ignored files. +This applies to filenames whose extensions are in +`diredp-compressed-extensions'. If nil they are highlighted using +face `diredp-compressed-file-name'. + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-image-preview-in-tooltip (or (and (boundp 'image-dired-thumb-size) image-dired-thumb-size) + 100) + "*Whether and what kind of image preview to show in a tooltip. +The possible values are: + + `nil' : do not show a tooltip preview + integer N>0 : show a thumbnail preview of that size + `full' : show a full-size preview of the image + +To enable tooltip image preview you must turn on `tooltip-mode' and +load library `image-dired.el'. See also option +`diredp-auto-focus-frame-for-thumbnail-tooltip-flag'. + +This option has no effect for Emacs versions prior to Emacs 22." + :type '(choice + (restricted-sexp :tag "Show a thumnail image of size" + :match-alternatives ((lambda (x) (and (wholenump x) (not (zerop x)))))) + (const :tag "Show a full-size image preview" full) + (const :tag "OFF: Do not show an image preview" nil)) + :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-image-show-this-file-use-frame-flag t + "Non-nil means `diredp-image-show-this-file' uses another frame. +If nil then it uses another window. Using another frame means you +have more control over the image size when you use a prefix arg. + +If it uses another window then the prefix arg controls only the +minimum window height, not necessarily the image scale (height). + +\(If the buffer displaying the image is already considered a +special-display buffer by your Emacs setup, then a nil value of this +option has no effect.)" + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-list-file-attributes (list '(5 8) 'auto) + "Which file attributes `diredp-list-file' uses, and when." + :group 'Dired-Plus :type '(list (repeat integer) + (choice + (const :tag "Show automatically, immediately" 'auto) + (const :tag "Show on demand via `l'" 'on-demand)))) + +;;;###autoload +(defcustom diredp-max-frames 200 + "*Max number of frames, for commands that find files in separate frames. +These commands are `dired-do-find-marked-files' and +`diredp-do-find-marked-files-recursive'. See their descriptions for +the circumstances in which they show the files in separate frames." + :type '(restricted-sexp :match-alternatives ((lambda (x) (and (wholenump x) (not (zerop x)))))) + :group 'Dired-Plus) + +(when (fboundp 'file-equal-p) ; Emacs 24+ + (defcustom diredp-move-file-dirs () + "Alist of names of files and preferred directories to move them to. +File names should be relative (no directory component). +Target directory names should be absolute." + :group 'files :type '(alist :key-type file :value-type directory))) + +;; (Not used - just use the body directly in the option default value. +;; (defun diredp-omit-files-regexp () +;; "Return regexp to use for font-locking, using `dired-omit-files' as base." +;; (let* ((strg dired-omit-files) +;; (strg (if (eq ?^ (aref strg 0)) (substring strg 1) strg)) ; Remove initial ^ +;; (strg (replace-regexp-in-string "\\(\\\\[|]\\)\\^" "\\1" strg 'FIXEDCASE nil)) ; Remove other ^'s +;; (strg (replace-regexp-in-string "\\([$]\\)" "" strg 'FIXEDCASE nil))) ; Remove $'s +;; strg)) + +(defcustom diredp-omit-files-regexp (let* ((strg dired-omit-files) + (strg (if (eq ?^ (aref strg 0)) ; Remove initial ^ + (substring strg 1) + strg)) + (strg (replace-regexp-in-string "\\(\\\\[|]\\)\\^" ; Remove other ^'s + "\\1" + strg + 'FIXEDCASE + nil)) + (strg (replace-regexp-in-string "\\([$]\\)" ; Remove $'s + "" + strg + 'FIXEDCASE + nil))) + strg) + "Regexp for font-locking file names to be omitted by `dired-omit-mode'. +The regexp is matched only against the file name, but the entire line +is highlighted (with face `diredp-omit-file-name'). + +The default value of this option differs from that of +`dired-omit-files' by removing \"^\" from the beginning, and \"$\" +from the end, of each regexp choice. (The default value of +`dired-omit-files', at least prior to Emacs 27, uses \"^\" and \"$\", +but it should not.) + +If you want to control the beginning and end of choice matches then +use \"\\`\" and \"\\'\" instead of \"^\" and \"$\". + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :group 'Dired-Plus :type 'regexp) + +;;;###autoload +(defcustom diredp-prompt-for-bookmark-prefix-flag nil + "*Non-nil means prompt for a prefix string for bookmark names." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-visit-ignore-regexps () + "Regexps matching file names for `diredp-visit-(next|previous)' to skip. +A file or directory name matching one of these regexps is skipped, +along with those with an extension in `diredp-visit-ignore-extensions'." + :type '(repeat regexp) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-visit-ignore-extensions '("elc") + "Extensions of file names for `diredp-visit-(next|previous)' to skip. +A file name with one of these extensions is skipped, along with those +matching a regexp in `diredp-visit-ignore-regexps'." + :type '(repeat string) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-w32-local-drives '(("C:" "Local disk")) + "*Local MS Windows drives that you want to use for `diredp-w32-drives'. +Each entry is a list (DRIVE DESCRIPTION), where DRIVE is the drive +name and DESCRIPTION describes DRIVE." + :type '(alist + :key-type (string :tag "Drive name") + :value-type (group (string :tag "Drive description"))) + :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-wrap-around-flag t + "*Non-nil means Dired \"next\" commands wrap around to buffer beginning." + :type 'boolean :group 'Dired-Plus) + +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (defvar diredp-hide-details-last-state diredp-hide-details-initially-flag + "Last `dired-hide-details-mode' value. +Initialized to the value of option `diredp-hide-details-initially-flag'.") + + (defvar diredp-hide-details-toggled nil + "Non-nil means you have already toggled hiding details in this buffer.") + (make-variable-buffer-local 'diredp-hide-details-toggled)) + +;; Same value as the default value of `icicle-re-no-dot'. +(defvar diredp-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*" + "Regexp that matches anything except `.' and `..'.") + +(defvar diredp-w32-drives-mode-map (let ((map (make-sparse-keymap))) + (define-key map "q" 'bury-buffer) + (define-key map "\r" 'widget-button-press) + (define-key map [mouse-2] 'widget-button-click) + map) + "Keymap for `diredp-w32-drives-mode'.") + +;;; $$$$$$ Starting with Emacs 22, *-move-to* is defvaraliased to *-listing-before*. +;;; But `files+.el' defines *-listing-before*, so we define it here too. +;;; (unless (> emacs-major-version 21) +;;; (defvar directory-listing-before-filename-regexp dired-move-to-filename-regexp +;;; "Regular expression to match up to the file name in a directory listing. +;;; The default value is designed to recognize dates and times +;;; regardless of the language.")) + +;;; Macros + + +;; Unlike `dired-mark-if': +;; +;; 1. Value returned and message indicate both the number matched and the number changed. +;; 2. Added optional arg PLURAL, for irregular plurals (e.g. "directories"). +;; +(defmacro diredp-mark-if (predicate singular &optional plural) + "Mark files for PREDICATE, according to `dired-marker-char'. +PREDICATE is evaluated on each line, with point at beginning of line. +SINGULAR is a singular noun phrase for the type of files being marked. +Optional arg PLURAL is a plural noun phrase for the type of files + being marked. +If PLURAL is nil then SINGULAR should end with a noun that can be +pluralized by adding `s'. + +Return nil if no files matched PREDICATE. +Otherwise return a cons (CHANGED . MATCHED), where: + CHANGED is the number of markings that were changed by the operation. + MATCHED is the number of files that matched PREDICATE." + `(let ((inhibit-read-only t) + changed matched) + (save-excursion + (setq matched 0 + changed 0) + (when ,singular (message "%s %s%s..." + (cond ((eq dired-marker-char ?\040) "Unmarking") + ((eq dired-del-marker dired-marker-char) "Flagging") + (t "Marking")) + (or ,plural (concat ,singular "s")) + (if (eq dired-del-marker dired-marker-char) " for deletion" ""))) + (goto-char (point-min)) + (while (not (eobp)) + (when ,predicate + (setq matched (1+ matched)) + (unless (eq dired-marker-char (char-after)) + (delete-char 1) (insert dired-marker-char) (setq changed (1+ changed)))) + (forward-line 1)) + (when ,singular (message "%s %s%s%s newly %s%s" + matched + (if (= matched 1) ,singular (or ,plural (concat ,singular "s"))) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq dired-marker-char ?\040) "un" "") + (if (eq dired-marker-char dired-del-marker) "flagged" "marked")))) + (and (> matched 0) (cons changed matched)))) + + +;; Just a helper function for `dired-map-over-marks'. +(defun diredp-get-file-or-dir-name (arg) + "Return name of next file or directory or nil if none. +Argument ARG: + `all-files-no-dirs' or nil means skip directories. + `all-files-no-dots' means skip `.' and `..'." + (let ((fname nil)) + (while (and (not fname) (not (eobp))) + (setq fname (dired-get-filename t t)) + (when (and fname (or (not arg) (eq arg 'all-files-no-dirs)) (file-directory-p fname)) + (setq fname nil)) + (when (and fname (eq arg 'all-files-no-dots) (or (member fname '("." "..")) + (diredp-string-match-p "/\\.\\.?$" fname))) + (setq fname nil)) + (forward-line 1)) + (forward-line -1) + fname)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Treat multiple `C-u' specially. +;; +(defmacro dired-map-over-marks (body arg &optional show-progress + distinguish-one-marked) + "Eval BODY with point on each marked line. Return a list of BODY's results. +If no marked file could be found, execute BODY on the current line. +ARG, if non-nil, specifies the files to use instead of the marked files. + If ARG is an integer, use the next ARG files (previous -ARG, if < 0). + In that case, point is dragged along. This is so that commands on + the next ARG (instead of the marked) files can be easily chained. + If ARG is a cons with element 16, 64, or 256, corresponding to + `C-u C-u', `C-u C-u C-u', or `C-u C-u C-u C-u', then use all files + in the Dired buffer, where: + 16 includes NO directories (including `.' and `..') + 64 includes directories EXCEPT `.' and `..' + 256 includes ALL directories (including `.' and `..') + If ARG is otherwise non-nil, use the current file. +If optional third arg SHOW-PROGRESS evaluates to non-nil, + redisplay the Dired buffer after each file is processed. + +No guarantee is made about the position on the marked line. BODY must +ensure this itself, if it depends on this. + +Search starts at the beginning of the buffer, thus the car of the list +corresponds to the line nearest the end of the buffer. This is also +true for (positive and negative) integer values of ARG. + +BODY should not be too long, because it is expanded four times. + +If DISTINGUISH-ONE-MARKED is non-nil, then return (t FILENAME) instead + of (FILENAME), if only one file is marked." + ;; WARNING: BODY must not add new lines before point - this may cause an + ;; endless loop. This warning should not apply any longer, sk 2-Sep-1991 14:10. + `(prog1 + (let ((inhibit-read-only t) + (newarg ,arg) + multi-C-u case-fold-search found results) + (when (and (consp newarg) (> (prefix-numeric-value newarg) 4)) + (setq newarg (case (prefix-numeric-value newarg) + (16 'all-files-no-dirs) ; `C-u C-u' + (64 'all-files-no-dots) ; `C-u C-u C-u' + (256 'all-files) ; `C-u C-u C-u C-u' + (t 'all-files-no-dirs)) + multi-C-u t)) + (if (and newarg (not multi-C-u)) + (if (integerp newarg) + (progn ; No `save-excursion', want to move point. + (dired-repeat-over-lines newarg #'(lambda () + (when ,show-progress (sit-for 0)) + (setq results (cons ,body results)))) + (if (< newarg 0) (nreverse results) results)) + ;; Non-nil, non-integer ARG means use current file: + (list ,body)) + (let ((regexp (dired-marker-regexp)) + next-position) + (save-excursion + (goto-char (point-min)) + ;; Remember position of next marked file before BODY can insert lines before the + ;; just found file, confusing us by finding the same marked file again and again... + (setq next-position (and (if multi-C-u + (diredp-get-file-or-dir-name newarg) + (re-search-forward regexp nil t)) + (point-marker)) + found (not (null next-position))) + (while next-position + (goto-char next-position) + (when ,show-progress (sit-for 0)) + (setq results (cons ,body results)) + ;; move after last match + (goto-char next-position) + (forward-line 1) + (set-marker next-position nil) + (setq next-position (and (if multi-C-u + (diredp-get-file-or-dir-name newarg) + (re-search-forward regexp nil t)) + (point-marker))))) + (when (and ,distinguish-one-marked (= (length results) 1)) + (setq results (cons t results))) + (if found results (list ,body))))) + ;; `save-excursion' loses, again + (dired-move-to-filename))) + +;; Same as `icicle-with-help-window' in `icicles-mac.el' +;; and `bmkp-with-help-window' in `bookmark+-mac.el'. +(defmacro diredp-with-help-window (buffer &rest body) + "`with-help-window', if available; else `with-output-to-temp-buffer'." + (if (fboundp 'with-help-window) + `(with-help-window ,buffer ,@body) + `(with-output-to-temp-buffer ,buffer ,@body))) + +(put 'diredp-with-help-window 'common-lisp-indent-function '(4 &body)) + +;;; Utility functions + +;; Same as `imenup-delete-if-not'. +;; +(defun diredp-delete-if-not (predicate xs) + "Remove all elements of list XS that do not satisfy PREDICATE. +This operation is destructive, reusing conses of XS whenever possible." + (while (and xs (not (funcall predicate (car xs)))) + (setq xs (cdr xs))) + (let ((cl-p xs)) + (while (cdr cl-p) + (if (not (funcall predicate (cadr cl-p))) (setcdr cl-p (cddr cl-p)) (setq cl-p (cdr cl-p))))) + xs) + +;; Same as `imenup-delete-if'. +;; +(defun diredp-delete-if (predicate xs) + "Remove all elements of list XS that satisfy PREDICATE. +This operation is destructive, reusing conses of XS whenever possible." + (while (and xs (funcall predicate (car xs))) + (setq xs (cdr xs))) + (let ((cl-p xs)) + (while (cdr cl-p) + (if (funcall predicate (cadr cl-p)) + (setcdr cl-p (cddr cl-p)) + (setq cl-p (cdr cl-p))))) + xs) + +;; Same as `tap-string-match-p' in `thingatpt+.el'. +(if (fboundp 'string-match-p) + (defalias 'diredp-string-match-p 'string-match-p) ; Emacs 23+ + (defun diredp-string-match-p (regexp string &optional start) + "Like `string-match', but this saves and restores the match data." + (save-match-data (string-match regexp string start)))) + +(if (fboundp 'looking-at-p) + (defalias 'diredp-looking-at-p 'looking-at-p) ; Emacs 23+ + (defun diredp-looking-at-p (regexp) + "Like `looking-at', but this saves and restores the match data." + (save-match-data (looking-at regexp)))) + +;; `dired-read-regexp' does not accept DEFAULT and HISTORY for older Emacsen, so use this. +(defun diredp-read-regexp (prompt &optional default history) + "Read a regexp. +HISTORY defaults to `dired-regexp-history'." + (setq history (or history 'dired-regexp-history)) + (if (fboundp 'read-regexp) + (read-regexp prompt default history) + (read-from-minibuffer prompt nil nil nil history default))) + +(if (fboundp 'delete-dups) + (defalias 'diredp-delete-dups 'delete-dups) + (defun diredp-delete-dups (list) + "Destructively remove `equal' duplicates from LIST. +Store the result in LIST and return it. LIST must be a proper list. +Of several `equal' occurrences of an element in LIST, the first +one is kept." + (let ((tail list)) + (while tail + (setcdr tail (delete (car tail) (cdr tail))) + (setq tail (cdr tail)))) + list)) + +(defun diredp-nonempty-region-p () + "Return non-nil if region is active and non-empty." + (and transient-mark-mode mark-active (mark) (> (region-end) (region-beginning)))) + +(defun diredp-get-image-filename (&optional localp no-error-if-not-filep) + "Return the image-file name on this line, or nil if no image file. +If not in Dired (or a mode derived from Dired), then test the entire +text of the current line as the file name. + +The optional args are the same as for `dired-get-filename'. They are +ignored if not in a Dired mode. + +\(Prior to Emacs 22, this function just returns nil.)" + (let ((file (if (derived-mode-p 'dired-mode) + (dired-get-filename localp no-error-if-not-filep) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) + (and file + (fboundp 'image-file-name-regexp) ; Emacs 22+, `image-file.el'. + (diredp-string-match-p (image-file-name-regexp) file) + file))) + +(defun diredp-root-directory-p (file) + "Return non-nil if FILE is a root directory." + (if (fboundp 'ange-ftp-root-dir-p) + (ange-ftp-root-dir-p (file-name-as-directory file)) + ;; This is essentially `ange-ftp-root-dir-p' applied to `file-name-as-directory'. + ;; If `ange-ftp-root-dir-p' changes, update this code. + (or (and (eq system-type 'windows-nt) (diredp-string-match-p "\\`[a-zA-Z]:[/\\]\\'" + (file-name-as-directory file))) + (string= "/" file)))) + +(defun diredp-parent-dir (file &optional relativep) + "Return the parent directory of FILE, or nil if none. +Optional arg RELATIVEP non-nil means return a relative name, that is, +just the parent component." + (let ((parent (file-name-directory (directory-file-name (expand-file-name file)))) + relparent) + (when relativep (setq relparent (file-name-nondirectory (directory-file-name parent)))) + (and (not (equal parent file)) (or relparent parent)))) + +(unless (fboundp 'derived-mode-p) ; Emacs 20, 21. + (defun derived-mode-p (&rest modes) + "Non-nil if the current major mode is derived from one of MODES. +Uses the `derived-mode-parent' property of the symbol to trace backwards." + (let ((parent major-mode)) + (while (and (not (memq parent modes)) (setq parent (get parent 'derived-mode-parent)))) + parent))) + +(defun diredp-ensure-mode () + "Raise an error if not in Dired or a mode derived from it." + (unless (derived-mode-p 'dired-mode) + (error "You must be in Dired or a mode derived from it to use this command"))) + +(defun diredp-ensure-bookmark+ () + (unless (require 'bookmark+ nil t) (error "This command requires library `bookmark+.el'"))) + + +(unless (fboundp 'dired-nondirectory-p) ; Emacs 20, 21. + (defun dired-nondirectory-p (file) + "Return non-nil if FILE is not a directory." + (not (file-directory-p file)))) + + +;;; Some of the redefinitions that follow are essentially unaltered vanilla Emacs code to be +;;; reloaded, to use the new definition of `dired-map-over-marks' here. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Pass non-nil second arg to `dired-get-filename' so we can include `.' and `..'. +;; 2. Doc string is updated to reflect the new ARG behavior. +;; 3. Allow, unlike vanilla Emacs, use of FILTER and DISTINGUISH-ONE-MARKED together. +;; +(defun dired-get-marked-files (&optional localp arg filter distinguish-one-marked error-if-none-p) + "Return names of the marked files and directories as a list of strings. +The list is in the same order as the buffer, that is, the car is the + first marked file. +Values returned are normally absolute file names. +Optional arg LOCALP as in `dired-get-filename'. +Optional second argument ARG specifies files to use instead of marked. + Usually ARG comes from the command's prefix arg. + If ARG is an integer, use the next ARG files (previous -ARG, if < 0). + If ARG is a cons with element 16, 64, or 256, corresponding to + `C-u C-u', `C-u C-u C-u', or `C-u C-u C-u C-u', then use all files + in the Dired buffer, where: + 16 includes NO directories (including `.' and `..') + 64 includes directories EXCEPT `.' and `..' + 256 includes ALL directories (including `.' and `..') + If ARG is otherwise non-nil, use the current file. +Optional third argument FILTER, if non-nil, is a function to select + some of the files: those for which (funcall FILTER FILENAME) is + non-nil. +If DISTINGUISH-ONE-MARKED is non-nil, then return (t FILENAME) instead + of (FILENAME) if only one file is marked (after any filtering by + FILTER). +If ERROR-IF-NONE-P is non-nil, signal an error if the list of files is + empty. If ERROR-IF-NONE-P is a string then it is the error message. + +Note that the Dired+ version of this function differs from the vanilla +version in these respects: + +* There are more possibilities for argument ARG (prefix argument). +* Directories `.' and `..' can be included as marked. +* You can use arguments FILTER and DISTINGUISH-ONE-MARKED together." + (let ((all (delq nil (save-excursion (dired-map-over-marks (dired-get-filename localp 'NO-ERROR-IF-NOT-FILEP) + arg + nil + distinguish-one-marked)))) + result) + (when (equal all '(t)) (setq all nil)) ; Added by vanilla Emacs 24+. + (if (and distinguish-one-marked (eq (car all) t)) + (if (not filter) + all + (and (funcall filter (cadr all)) (list t (cadr all)))) + (dolist (file all) + (when (or (not filter) (funcall filter file)) (push file result))) + (when (and (null result) error-if-none-p) + (diredp-user-error (if (stringp error-if-none-p) error-if-none-p "No files specified"))) + result))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Define here to make use of my `dired-map-over-marks'. +;; 2. Added &rest arg FUN-ARGS. +;; 3. Added doc string. +;; +(defun dired-map-over-marks-check (fun mark-arg op-symbol &optional show-progress &rest fun-args) + "Map FUN over marked lines and display failures. +FUN returns non-nil (the offending object, e.g. the short form of the +filename) for a failure and probably logs a detailed error explanation +using function `dired-log'. + +MARK-ARG is as the second argument of `dired-map-over-marks'. + +OP-SYMBOL is a symbol describing the operation performed (e.g. +`compress'). It is used with `dired-mark-pop-up' to prompt the user +\(e.g. with `Compress * [2 files]? ') and to display errors (e.g. +`Failed to compress 1 of 2 files - type ? for details (\"foo\")') + +SHOW-PROGRESS if non-nil means redisplay Dired after each file. + +FUN-ARGS is the list of any remaining args to +`dired-map-over-marks-check'. Function FUN is applied to these +arguments." + (and (dired-mark-confirm op-symbol mark-arg) + (let* ((results (dired-map-over-marks (apply fun fun-args) mark-arg show-progress)) ; FUN return vals. + (nb-results (length results)) + (failures (delq nil results)) + (nb-fail (length failures)) + (op-strg (if (eq op-symbol 'compress) "Compress or uncompress" (capitalize + (symbol-name op-symbol))))) + (if (null failures) + (message "%s: %d file%s." op-strg nb-results (dired-plural-s nb-results)) + (dired-log-summary (format "Failed to %s %d of %d file%s" + (downcase op-strg) nb-fail nb-results (dired-plural-s nb-results)) + failures))))) + +;; Like `dired-map-over-marks-check', but `dired-log-summary' is always called, and first arg passed is different. +;; +(defun diredp-map-over-marks-and-report (fun mark-arg op-symbol &optional show-progress &rest fun-args) + "Map FUN over marked lines and report the results. +FUN returns non-nil (the offending object, e.g. the short form of the +filename) for a failure and probably logs a detailed error explanation +using function `dired-log'. + +MARK-ARG is as the second argument of `dired-map-over-marks'. + +OP-SYMBOL is a symbol describing the operation performed (e.g. +`compress'). It is used with `dired-mark-pop-up' to prompt the user +\(e.g. with `Compress * [2 files]? ') and to display errors (e.g. +`Failed to compress 1 of 2 files - type ? to see why (\"foo\")') + +SHOW-PROGRESS if non-nil means redisplay Dired after each file. + +FUN-ARGS is the list of any remaining args to +`diredp-map-over-marks-and-report'. Function FUN is applied to these +arguments." + (and (dired-mark-confirm op-symbol mark-arg) + (let* ((results (dired-map-over-marks (apply fun fun-args) mark-arg show-progress)) ; FUN return vals. + (nb-results (length results)) + (failures (delq nil results)) + (nb-fail (length failures)) + (op-strg (capitalize (symbol-name op-symbol)))) + (dired-log-summary (format "%s for %d file%s%s" + op-strg nb-results (dired-plural-s nb-results) + (if failures (format ": %d failures" nb-fail) "")) + failures)))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +(when (boundp 'dired-subdir-switches) ; Emacs 22+ + (defun dired-do-redisplay (&optional arg test-for-subdir) ; Bound to `l' + "Redisplay all marked (or next ARG) files. +If on a subdir line, redisplay that subdirectory. In that case, +a prefix arg lets you edit the `ls' switches used for the new listing. + +Dired remembers switches specified with a prefix arg, so that reverting +the buffer will not reset them. However, using `dired-undo' to re-insert +or delete subdirectories can bypass this machinery. Hence, you sometimes +may have to reset some subdirectory switches after a `dired-undo'. +You can reset all subdirectory switches to the default using +\\<dired-mode-map>\\[dired-reset-subdir-switches]. +See Info node `(emacs)Subdir switches' for more details." + ;; Moves point if the next ARG files are redisplayed. + (interactive "P\np") + (if (and test-for-subdir (dired-get-subdir)) + (let* ((dir (dired-get-subdir)) + (switches (cdr (assoc-string dir dired-switches-alist)))) + (dired-insert-subdir dir (and arg (read-string "Switches for listing: " + (or switches + dired-subdir-switches + dired-actual-switches))))) + (message "Redisplaying...") + ;; `message' is much faster than making `dired-map-over-marks' show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks (let ((fname (dired-get-filename)) + ;; Postpone readin hook map over all marked files (Bug#6810). + (dired-after-readin-hook nil)) + (message "Redisplaying... `%s'" fname) + (dired-update-file-line fname)) + arg) + (run-hooks 'dired-after-readin-hook) + (dired-move-to-filename) + (message "Redisplaying...done")))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +(unless (boundp 'dired-subdir-switches) ; Emacs 20, 21 + (defun dired-do-redisplay (&optional arg test-for-subdir) ; Bound to `l' + "Redisplay all marked (or next ARG) files. +If on a subdir line, redisplay that subdirectory. In that case, +a prefix arg lets you edit the `ls' switches used for the new listing." + ;; Moves point if the next ARG files are redisplayed. + (interactive "P\np") + (if (and test-for-subdir (dired-get-subdir)) + (dired-insert-subdir (dired-get-subdir) + (and arg (read-string "Switches for listing: " dired-actual-switches))) + (message "Redisplaying...") + ;; `message' is much faster than making dired-map-over-marks show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks (let ((fname (dired-get-filename))) + (message "Redisplaying... `%s'" fname) + (dired-update-file-line fname)) + arg) + (dired-move-to-filename) + (message "Redisplaying...done")))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +(when (fboundp 'get-window-with-predicate) ; Emacs 22+ + (defun dired-dwim-target-directory () + "Guess a target directory to use for Dired. +If there is a Dired buffer displayed in another window, use its +current subdir, else use current subdir of this Dired buffer." + (let ((this-dir (and (eq major-mode 'dired-mode) (dired-current-directory)))) + ;; Non-dired buffer may want to profit from this function, e.g. `vm-uudecode'. + (if dired-dwim-target + (let* ((other-win (get-window-with-predicate (lambda (window) + (with-current-buffer (window-buffer window) + (eq major-mode 'dired-mode))) + nil + (and diredp-dwim-any-frame-flag 'visible))) + (other-dir (and other-win (with-current-buffer (window-buffer other-win) + (and (eq major-mode 'dired-mode) (dired-current-directory)))))) + (or other-dir this-dir)) + this-dir)))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Added behavior for non-positive prefix arg: +;; * Construct a cons DIRNAME arg. +;; * Read a Dired buffer name (not a directory) for its car. +;; * If READ-EXTRA-FILES-P is non-nil then read any number of file and dir names, to be included as its cdr. +;; * If chosen Dired buffer exists and is an ordinary listing then start out with its `directory-files'. +;; +;; 2. If you use Icicles then this is a multi-command - see doc for `dired' defadvice. +;; +(defun dired-read-dir-and-switches (string &optional read-extra-files-p dired-buffer) + "Read arguments for `dired' commands. +STRING is added to the prompt after \"Dired \". If not \"\", it should +end with a space. + +With a non-negative prefix arg, read the `ls' switches. +With a non-negative prefix arg or none, read the directory to Dired. + +With a non-positive prefix arg: +* If DIRED-BUFFER is non-nil, it is the name of the Dired buffer to + use. Otherwise, read it (it is not necessarily a directory name). + If in Dired now, the current buffer name is the default. +* If READ-EXTRA-FILES-P is non-nil then read any number of directory + or file names, to make up the Dired arbitrary-files listing. You + can use file-name wildcards (i.e., `*' for globbing), to include the + matching files and directories. Use `C-g' when done entering the + files and directories to list. + +Return a list of arguments for `dired': (DIRNAME SWITCHES). DIRNAME +here has the same form as `dired-directory'. When a non-positive +prefix arg is used, DIRNAME is a cons of the buffer name and the list +of file names. + +If you use Icicles then reading uses Icicles completion, with +additional multi-command keys. See `dired' (defadvice doc)." + (let* ((switchs (and current-prefix-arg + (natnump (prefix-numeric-value current-prefix-arg)) + (read-string "Dired listing switches: " + dired-listing-switches))) + (icicle-candidate-action-fn + (lambda (cand) + (dired-other-window cand (and current-prefix-arg (read-string "Dired listing switches: " + dired-listing-switches))) + (select-window (minibuffer-window)) + (select-frame-set-input-focus (selected-frame)))) +;;; $$$$$$ Alternative: Could choose no-op for non-dir candidate. +;;; (icicle-candidate-action-fn +;;; (lambda (cand) +;;; (cond ((file-directory-p cand) +;;; (dired-other-window cand (and current-prefix-arg (read-string "Dired listing switches: " +;;; dired-listing-switches))) +;;; (select-window (minibuffer-window)) +;;; (select-frame-set-input-focus (selected-frame))) +;;; (t +;;; (message "Not a directory: `%s'" cand) (sit-for 2))))) + (icicle-all-candidates-list-alt-action-fn ; M-|' + (lambda (files) + (let ((enable-recursive-minibuffers t)) + (dired-other-window (cons (read-string (format "Dired %s(buffer name): " string)) files))))) + (icicle-sort-comparer (or (and (boundp 'icicle-file-sort) ; If not reading files + icicle-file-sort) ; then dirs first. + (and (> (prefix-numeric-value current-prefix-arg) 0) + 'icicle-dirs-first-p) + (and (boundp 'icicle-sort-comparer) + icicle-sort-comparer))) + + ;; The rest of the bindings are from `icicle-file-bindings', in `icicles-mac.el'. + (completion-ignore-case + (or (and (boundp 'read-file-name-completion-ignore-case) read-file-name-completion-ignore-case) + completion-ignore-case)) + (icicle-show-Completions-initially-flag (and (boundp 'icicle-show-Completions-initially-flag) + (or icicle-show-Completions-initially-flag + icicle-files-ido-like-flag))) + (icicle-top-level-when-sole-completion-flag (and (boundp 'icicle-top-level-when-sole-completion-flag) + (or icicle-top-level-when-sole-completion-flag + icicle-files-ido-like-flag))) + (icicle-default-value (and (boundp 'icicle-default-value) + (if (and icicle-files-ido-like-flag + icicle-default-value) + icicle-files-ido-like-flag + ;; Get default via `M-n', but do not insert it. + (and (memq icicle-default-value '(t nil)) + icicle-default-value)))) + (icicle-must-match-regexp (and (boundp 'icicle-file-match-regexp) + icicle-file-match-regexp)) + (icicle-must-not-match-regexp (and (boundp 'icicle-file-no-match-regexp) + icicle-file-no-match-regexp)) + (icicle-must-pass-after-match-predicate (and (boundp 'icicle-file-predicate) + icicle-file-predicate)) + (icicle-require-match-flag (and (boundp 'icicle-file-require-match-flag) + icicle-file-require-match-flag)) + (icicle-file-completing-p t) + (icicle-extra-candidates (and (boundp 'icicle-file-extras) icicle-file-extras)) + (icicle-transform-function 'icicle-remove-dups-if-extras) + ;; Put `icicle-file-sort' first. If already in the list, move it, else add it, to beginning. + (icicle--temp-orders (and (boundp 'icicle-sort-orders-alist) + (copy-sequence icicle-sort-orders-alist))) + (icicle-candidate-help-fn (lambda (cand) + (icicle-describe-file cand current-prefix-arg t))) + (icicle-candidate-alt-action-fn (and (boundp 'icicle-candidate-alt-action-fn) + (or icicle-candidate-alt-action-fn + (icicle-alt-act-fn-for-type "file")))) + (icicle-delete-candidate-object 'icicle-delete-file-or-directory) + (icicle-sort-orders-alist + (and (boundp 'icicle-sort-orders-alist) + (progn (when t ; $$$$ (and icicle-file-sort-first-time-p icicle-file-sort) + (setq icicle-sort-comparer icicle-file-sort)) + ; $$$$ (setq icicle-file-sort-first-time-p nil)) + (if icicle-file-sort + (let ((already-there (rassq icicle-file-sort icicle--temp-orders))) + (if already-there + (cons already-there (setq icicle--temp-orders (delete already-there + icicle--temp-orders))) + (cons `("by `icicle-file-sort'" ,@icicle-file-sort) icicle--temp-orders))) + icicle--temp-orders))))) + (when (fboundp 'icicle-bind-file-candidate-keys) (icicle-bind-file-candidate-keys)) + (unwind-protect + (list + (if (> (prefix-numeric-value current-prefix-arg) 0) + ;; If a dialog box is about to be used, call `read-directory-name' so the dialog + ;; code knows we want directories. Some dialog boxes can only select directories + ;; or files when popped up, not both. If no dialog box is used, call `read-file-name' + ;; because the user may want completion of file names for use in a wildcard pattern. + (funcall (if (and (fboundp 'read-directory-name) (next-read-file-uses-dialog-p)) + #'read-directory-name + #'read-file-name) + (format "Dired %s(directory): " string) nil default-directory nil) + (dolist (db dired-buffers) ; Remove any killed buffers from `dired-buffers' (even if DIRED-BUFFER). + (unless (buffer-name (cdr db)) (setq dired-buffers (delq db dired-buffers)))) + (let* ((dbufs (and (not dired-buffer) + (mapcar (lambda (db) (list (buffer-name (cdr db)))) dired-buffers))) + (dirbuf (or dired-buffer + (completing-read (format "Dired %s(buffer name): " string) dbufs nil nil nil nil + (and (derived-mode-p 'dired-mode) (buffer-name))))) + (files (and (diredp-existing-dired-buffer-p dirbuf) + (with-current-buffer (get-buffer dirbuf) + (and (not (consp dired-directory)) + (directory-files dired-directory 'FULL diredp-re-no-dot))))) + file) + (when read-extra-files-p + (while (condition-case nil ; Use lax completion, to allow wildcards. + (setq file (read-file-name "File or dir (C-g when done): ")) + (quit nil)) + ;; Do not allow root dir (`/' or a Windows drive letter, e.g. `d:/'). + (if (diredp-root-directory-p file) + (progn (message "Cannot choose root directory") (sit-for 1)) + (push file files)))) + (cons dirbuf files))) + switchs) + (when (fboundp 'icicle-unbind-file-candidate-keys) (icicle-unbind-file-candidate-keys))))) + + +;;; $$$$$$$$ An alternative implementation - different behavior. +;;; +;;; ;; REPLACE ORIGINAL in `dired.el'. +;;; ;; +;;; ;; Non-positive prefix arg means construct cons DIRNAME arg: Read Dired name and files/dirs. +;;; ;; +;;; (defun dired-read-dir-and-switches (string) +;;; "Read arguments for `dired'. +;;; With a non-negative prefix arg, prompt first for `ls' switches. +;;; With a non-positive prefix arg, read the Dired buffer name and then +;;; read any number of dir or file names, to make up the Dired listing. + +;;; STRING is appended to the prompt, unless prefix arg is non-positive. +;;; If non-empty, STRING should begin with a SPC." +;;; (let ((switches (and current-prefix-arg +;;; (>= (prefix-numeric-value current-prefix-arg) 0) +;;; (read-string "Dired listing switches: " dired-listing-switches))) +;;; (formt (format "Dired %s(directory): " string)) +;;; (entries ()) +;;; (curr-entry "")) +;;; (when (and current-prefix-arg (<= (prefix-numeric-value current-prefix-arg) 0)) +;;; (push (completing-read "Dired buffer name: " dired-buffers) entries) +;;; (setq curr-entry (read-file-name (format "Dir or file: ") nil "" 'MUST-MATCH)) +;;; (while (not (equal "" curr-entry)) +;;; (push curr-entry entries) +;;; (setq curr-entry (read-file-name (format "Dir or file: ") nil "" 'MUST-MATCH))) +;;; (unless (cadr entries) (push default-directory entries))) +;;; (list (or (nreverse entries) (if (and (fboundp 'next-read-file-uses-dialog-p) +;;; (next-read-file-uses-dialog-p)) +;;; (read-directory-name formt nil default-directory nil) +;;; (read-file-name formt nil default-directory nil))) +;;; switches))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior as follows: + +* If >= 0, you are first prompted for the `ls' switches to use. + +* If <= 0, you are prompted first for the name of the Dired buffer. + Then you are prompted repeatedly for the names of the directories + or files to list in the buffer. You can use file-name wildcards + (i.e., `*' for globbing), to include the matching files and + directories. Use `C-g' to end. + + In other words, instead of listing a single directory, the Dired + buffer can list any number of directories and file names, which can + even belong to different directory trees. + +The rest of this description applies only if you use Icicles. + +In Icicle mode this is a multi-command: You can cycle among file-name +completion candidates and act individually on those that name +directories. The action is to open Dired for the directory. While +cycling, these keys are active: + +\\<minibuffer-local-completion-map>\ +`C-mouse-2', `C-return' - Act on current completion candidate only +`C-down', `C-wheel-down' - Move to next completion candidate and act +`C-up', `C-wheel-up' - Move to previous completion candidate and act +`C-next' - Move to next apropos-completion candidate and act +`C-prior' - Move to previous apropos-completion candidate and act +`C-end' - Move to next prefix-completion candidate and act +`C-home' - Move to previous prefix-completion candidate and act +`\\[icicle-all-candidates-action]' - Act on *all* candidates, successively (careful!) +`\\[icicle-all-candidates-list-alt-action]' - Open Dired on all candidates + +When candidate action and cycling are combined (e.g. `C-next'), user +option `icicle-act-before-cycle-flag' determines which occurs first. + +With prefix `C-M-' instead of `C-', the same keys (`C-M-mouse-2', +`C-M-RET', `C-M-down', and so on) provide help about candidates. + +Use `mouse-2', `RET', or `S-RET' to finally choose a candidate, or +`C-g' to quit. + +These keys are also bound in the minibuffer during completion (`*' +means the key requires library `Bookmark+'): + + S-delete - Delete candidate file or (empty) dir + C-c + - Create a new directory + C-backspace - Go up one directory level + * C-x C-t * - Narrow to files with all of the tags you specify + * C-x C-t + - Narrow to files with some of the tags you specify + * C-x C-t % * - Narrow to files with all tags matching a regexp + * C-x C-t % + - Narrow to files with some tags matching a regexp + * C-x a + - Add tags to the current-candidate file + * C-x a - - Remove tags from the current-candidate file + * C-x m - Access file bookmarks (not just autofiles)" + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired-other-window (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior. +A non-positive prefix arg lets you choose an explicit set of files and +directories to list. See the advice for `dired' for more information." + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired-other-frame (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior. +A non-positive prefix arg lets you choose an explicit set of files and +directories to list. See the advice for `dired' for more information." + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Made compatible with Emacs 20, 21, which do not have [:alnum]. +;; Also, this is defined here because it is used elsewhere in the file. +;; +(defun dired-switches-escape-p (switches) + "Return non-nil if the string SWITCHES contains `-b' or `--escape'." + (if (fboundp 'dired-switches-check) ; Emacs 24.4+ - see Emacs bug #17218. + (dired-switches-check switches "escape" "b") + ;; Do not match things like "--block-size" that happen to contain "b". + (if (> emacs-major-version 21) ; SWITCHES must be a string here, not nil. + (diredp-string-match-p "\\(\\`\\| \\)-[[:alnum:]]*b\\|--escape\\>" switches) + (diredp-string-match-p "\\(\\`\\| \\)-\\(\w\\|[0-9]\\)*b\\|--escape\\>" switches)))) + + +;; From `dired.el' + +(when (and (> emacs-major-version 22) (featurep 'ls-lisp+)) + +;;; 2012/04/26: Commented this out. +;;; Might need it again when update `ls-lisp+.el' to fix other things. +;;; +;;; ;; Use t as WILDCARD arg to `dired-insert-directory'. +;;; ;; +;;; (defun dired-readin-insert () +;;; ;; Insert listing for the specified dir (and maybe file list) +;;; ;; already in dired-directory, assuming a clean buffer. +;;; (let (dir file-list) +;;; (if (consp dired-directory) +;;; (setq dir (car dired-directory) +;;; file-list (cdr dired-directory)) +;;; (setq dir dired-directory +;;; file-list ())) +;;; (setq dir (expand-file-name dir)) +;;; (if (and (equal "" (file-name-nondirectory dir)) (not file-list)) +;;; ;; If we are reading a whole single directory... +;;; (dired-insert-directory dir dired-actual-switches nil nil t) +;;; (unless (file-readable-p (directory-file-name (file-name-directory dir))) +;;; (error "Directory `%s' inaccessible or nonexistent" dir)) +;;; ;; Else treat it as a wildcard spec. +;;; (dired-insert-directory dir dired-actual-switches file-list t t)))) + + + ;; REPLACE ORIGINAL in `dired.el'. + ;; + ;; Compute WILDCARD arg for `insert-directory' for individual file (don't just use nil). + ;; + (defun dired-insert-directory (dir switches &optional file-list wildcard hdr) + "Insert a directory listing of DIR, Dired style. +Use SWITCHES to make the listings. +If FILE-LIST is non-nil, list only those files. +Otherwise, if WILDCARD is non-nil, expand wildcards; + in that case, DIR should be a file name that uses wildcards. +In other cases, DIR should be a directory name or a directory filename. +If HDR is non-nil, insert a header line with the directory name." + (let ((opoint (point)) + (process-environment (copy-sequence process-environment)) + end) + (when (and + ;; Do not try to invoke `ls' if on DOS/Windows, where `ls-lisp' is used, unless + ;; the user really wants to use `ls', as indicated by + ;; `ls-lisp-use-insert-directory-program'. + (or (not (featurep 'ls-lisp)) ls-lisp-use-insert-directory-program) + (or (if (eq dired-use-ls-dired 'unspecified) + ;; Check if "ls --dired" gives exit code 0. Put it in `dired-use-ls-dired'. + (or (setq dired-use-ls-dired (eq 0 (call-process insert-directory-program + nil nil nil "--dired"))) + (progn (message "Command `ls' does not support switch `--dired' - see \ +`dired-use-ls-dired'.") + nil)) + dired-use-ls-dired) + (file-remote-p dir))) + (setq switches (concat "--dired " switches))) + ;; We used to specify the C locale here, to force English month names. This should not be + ;; necessary any more with the new value of `directory-listing-before-filename-regexp'. + (if file-list + (dolist (f file-list) + (let ((beg (point))) + ;; Compute wildcard arg for this file. + (insert-directory f switches (diredp-string-match-p "[[?*]" f) nil) + ;; Re-align fields, if necessary. + (dired-align-file beg (point)))) + (insert-directory dir switches wildcard (not wildcard))) + ;; Quote certain characters, unless `ls' quoted them for us. + (unless (dired-switches-escape-p dired-actual-switches) + (save-excursion + (setq end (point-marker)) + (goto-char opoint) + (while (search-forward "\\" end t) + (replace-match (apply #'propertize "\\\\" (text-properties-at (match-beginning 0))) + nil t)) + (goto-char opoint) + (while (search-forward "\^m" end t) + (replace-match (apply #'propertize "\\015" (text-properties-at (match-beginning 0))) + nil t)) + (set-marker end nil)) + ;; Comment in original, from some Emacs Dev developer: + ;; + ;; Replace any newlines in DIR with literal "\n" for the sake of the header line. To + ;; disambiguate a literal "\n" in the actual dirname, we also replace "\" with "\\". + ;; I think this should always be done, irrespective of the value of + ;; dired-actual-switches, because: + ;; i) Dired does not work with an unescaped newline in the directory name used in the + ;; header (bug=10469#28), and + ;; ii) "\" is always replaced with "\\" in the listing, so doing it in the header as + ;; well makes things consistent. + ;; But at present it is done only if "-b" is in ls-switches, because newlines in dirnames + ;; are uncommon, and people may have gotten used to seeing unescaped "\" in the headers. + ;; Note: adjust `dired-build-subdir-alist' if you change this. + (setq dir (replace-regexp-in-string "\\\\" "\\\\" dir nil t) + dir (replace-regexp-in-string "\n" "\\n" dir nil t))) + ;; If we used `--dired' and it worked, the lines are already indented. Else indent them. + (unless (save-excursion (goto-char opoint) (diredp-looking-at-p " ")) + (let ((indent-tabs-mode nil)) (indent-rigidly opoint (point) 2))) + ;; Insert text at the beginning to standardize things. + (let ((content-point opoint)) + (save-excursion + (goto-char opoint) + (when (and (or hdr wildcard) (not (and (looking-at "^ \\(.*\\):$") + (file-name-absolute-p (match-string 1))))) + ;; `dired-build-subdir-alist' will replace the name by its expansion, so it does not + ;; matter whether what we insert here is fully expanded, but it should be absolute. + (insert " " (directory-file-name (file-name-directory dir)) ":\n") + (setq content-point (point))) + (when wildcard + ;; Insert "wildcard" line where "total" line would be for a full dir. + (insert " wildcard " (file-name-nondirectory dir) "\n"))) + (dired-insert-set-properties content-point (point)))))) + + +;;; Image stuff. + +(defun diredp-image-dired-required-msg () + "Raise an error if `image-dired.el' is not loaded." + (unless (require 'image-dired nil t) (error "This command requires library `image-dired.el'"))) + +;; See `image-dired-create-thumb'. +;; Define this even if `image-dired.el' is not loaded. +;; Do NOT raise an error if not loaded, because this is used in `diredp-mouseover-help'. +;;;###autoload +(defun diredp-image-dired-create-thumb (file &optional arg) + "Create thumbnail image file for FILE (default: file on current line). +With a prefix arg, replace any existing thumbnail for FILE. +With a numeric prefix arg (not a cons), use it as the thumbnail size. +Return the name of the thumbnail image file, or nil if none." + (interactive (list (if (derived-mode-p 'dired-mode) + (dired-get-filename nil 'NO-ERROR) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) (line-end-position))) + current-prefix-arg)) + (and (fboundp 'image-dired-thumb-name) ; No-op (return nil) if `image-dired.el' not loaded. + (let ((thumb-name (image-dired-thumb-name file))) + (when arg (clear-image-cache)) + (when (or arg (not (file-exists-p thumb-name))) + (let ((image-dired-thumb-width (or (and arg (atom arg) arg) image-dired-thumb-width)) + (image-dired-thumb-height (or (and arg (atom arg) arg) image-dired-thumb-height))) + (unless (zerop (image-dired-create-thumb file thumb-name)) + (error "Thumbnail image file could not be created")))) + (and (file-exists-p thumb-name) thumb-name)))) + + +;; REPLACE ORIGINAL in `image-dired.el' (Emacs 22-23). +;; +;; 1. Raise an error if `image-dired.el' is not available. +;; 2. Repro it here so it picks up `Dired+' version of `dired-map-over-marks'. +;; +;;;###autoload +(defun image-dired-dired-insert-marked-thumbs () ; Bound to `C-t C-t' (Emacs 22-23) + "Insert thumbnails before file names of marked files in the Dired buffer." + (interactive (progn (diredp-image-dired-required-msg) ())) + (dired-map-over-marks + (let* ((image-pos (dired-move-to-filename)) + (image-file (dired-get-filename)) + (thumb-file (image-dired-get-thumbnail-image image-file)) + overlay) + ;; If image is not already added, then add it. + (unless (delq nil (mapcar (lambda (o) (overlay-get o 'put-image)) + ;; Can't use (overlays-at (point)), BUG? + (overlays-in (point) (1+ (point))))) + (put-image thumb-file image-pos) + (setq overlay (car (delq nil (mapcar (lambda (ov) (and (overlay-get ov 'put-image) ov)) + (overlays-in (point) (1+ (point))))))) + (overlay-put overlay 'image-file image-file) + (overlay-put overlay 'thumb-file thumb-file))) + nil) + (add-hook 'dired-after-readin-hook 'image-dired-dired-after-readin-hook nil t)) + + +;; REPLACE ORIGINAL in `image-dired.el' (Emacs 24+). +;; +;; 1. Raise an error if `image-dired.el' is not available. +;; 2. Repro it here so it picks up `Dired+' version of `dired-map-over-marks'. +;; +;;;###autoload +(defun image-dired-dired-toggle-marked-thumbs (&optional arg) ; Bound to `C-t C-t' (Emacs 24+) + "Toggle thumbnails in front of file names in Dired. +If no files are marked, insert or hide thumbnails on the current line. +With a numeric prefix arg N, ignore marked files and act on the next N +files (previous -N files, if N < 0)." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (dired-map-over-marks + (let* ((image-pos (dired-move-to-filename)) + (image-file (diredp-get-image-filename nil 'NO-ERROR)) + thumb-file overlay) + (when image-file + (setq thumb-file (image-dired-get-thumbnail-image image-file)) + ;; If image is not already added, then add it. + (let* ((cur-ovs (overlays-in (point) (1+ (point)))) + (thumb-ov (car (diredp-remove-if-not (lambda (ov) (overlay-get ov 'thumb-file)) + cur-ovs)))) + (if thumb-ov + (delete-overlay thumb-ov) + (put-image thumb-file image-pos) + (setq overlay (car (delq nil (mapcar (lambda (ov) (and (overlay-get ov 'put-image) ov)) + (overlays-in (point) (1+ (point))))))) + (overlay-put overlay 'image-file image-file) + (overlay-put overlay 'thumb-file thumb-file))))) + arg + 'SHOW-PROGRESS) + (add-hook 'dired-after-readin-hook 'image-dired-dired-after-readin-hook nil t)) + +;; Corresponds to `image-dired-dired-comment-files'. +;;;###autoload +(defun diredp-image-dired-comment-file () + "Add comment to this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-write-comments (cons (dired-get-filename) (image-dired-read-comment)))) + +;; Corresponds to `image-dired-tag-files'. +;;;###autoload +(defun diredp-image-dired-tag-file () + "Tag this image file with an `image-dired' tag." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-write-tags (cons (dired-get-filename) + (read-string "Tags to add (use `;' to separate): ")))) + +;; Corresponds to `image-dired-delete-tag'. +;;;###autoload +(defun diredp-image-dired-delete-tag () + "Remove an `image-dired' tag from this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-remove-tag (list (dired-get-filename)) (read-string "Tag to remove: "))) + +;; Corresponds to `image-dired-display-thumbs'. +;;;###autoload +(defun diredp-image-dired-display-thumb (&optional append) + "Pop to thumbnail of this image file, in `image-dired-thumbnail-buffer'. +If a thumbnail image does not yet exist for this file, create it. +With a prefix arg, append the thumbnail to the thumbnails buffer, +instead of clearing the buffer first." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (let* ((dired-buf (current-buffer)) + (curr-file (dired-get-filename)) + (thumb-name (image-dired-thumb-name curr-file))) + (with-current-buffer (image-dired-create-thumbnail-buffer) + (let ((inhibit-read-only t)) + (if (not append) (erase-buffer) (goto-char (point-max))) + (if (and (not (file-exists-p thumb-name)) + (not (zerop (image-dired-create-thumb curr-file thumb-name)))) + (message "Cannot create thumbnail image for file `%s'" curr-file) + (image-dired-insert-thumbnail thumb-name curr-file dired-buf))) + (cond ((eq 'dynamic image-dired-line-up-method) (image-dired-line-up-dynamic)) + ((eq 'fixed image-dired-line-up-method) (image-dired-line-up)) + ((eq 'interactive image-dired-line-up-method) (image-dired-line-up-interactive)) + ((eq 'none image-dired-line-up-method) nil) + (t (image-dired-line-up-dynamic)))) + (pop-to-buffer image-dired-thumbnail-buffer))) + +;; Corresponds to `image-dired-copy-with-exif-file-name'. +;;;###autoload +(defun diredp-image-dired-copy-with-exif-name () + "Copy this image file to your main image directory. +Uses `image-dired-get-exif-file-name' to name the new file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (let* ((curr-file (dired-get-filename)) + (new-name (format "%s/%s" (file-name-as-directory + (expand-file-name image-dired-main-image-directory)) + (image-dired-get-exif-file-name curr-file)))) + (message "Copying `%s' to `%s'..." curr-file new-name) + (copy-file curr-file new-name) + (message "Copying `%s' to `%s'...done" curr-file new-name))) + +;; Corresponds to `image-dired-dired-edit-comment-and-tags'. +;;;###autoload +(defun diredp-image-dired-edit-comment-and-tags () + "Edit comment and tags for this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (setq image-dired-widget-list ()) + (let ((file (dired-get-filename))) + (if (fboundp 'pop-to-buffer-same-window) + (pop-to-buffer-same-window "*Image-Dired Edit Meta Data*") + (switch-to-buffer "*Image-Dired Edit Meta Data*")) + (kill-all-local-variables) + (make-local-variable 'widget-example-repeat) + (let ((inhibit-read-only t)) + (erase-buffer) + (remove-overlays) + (widget-insert + "\nEdit comment and tags for the image. Separate multiple tags +with a comma (`,'). Move forward among fields using `TAB' or `RET'. +Move backward using `S-TAB'. Click `Save' to save your edits or +`Cancel' to abandon them.\n\n") + (let* ((thumb-file (image-dired-thumb-name file)) + (img (create-image thumb-file)) + comment-widget tag-widget) + (insert-image img) + (widget-insert "\n\nComment: ") + (setq comment-widget (widget-create 'editable-field :size 60 :format "%v " + :value (or (image-dired-get-comment file) ""))) + (widget-insert "\nTags: ") + (setq tag-widget (widget-create 'editable-field :size 60 :format "%v " + :value (or (mapconcat #'identity (image-dired-list-tags file) ",") ""))) + ;; Save info in widgets to use when the user saves the form. + (setq image-dired-widget-list (append image-dired-widget-list + (list (list file comment-widget tag-widget)))) + (widget-insert "\n\n"))) + (widget-insert "\n") + (widget-create 'push-button :notify (lambda (&rest _ignore) + (image-dired-save-information-from-widgets) + (bury-buffer) + (message "Done")) + "Save") + (widget-insert " ") + (widget-create 'push-button :notify (lambda (&rest _ignore) + (bury-buffer) + (message "Operation canceled")) + "Cancel") + (widget-insert "\n") + (use-local-map widget-keymap) + (widget-setup) + (widget-forward 1))) ; Jump to the first widget. + +;;;###autoload +(defun diredp-do-display-images (&optional arg) + "Display the marked image files. +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any files are marked). + More than one `C-u' means use all files in the Dired buffer, as if + they were all marked." + (interactive (progn (unless (require 'image-file nil t) + (error "This command requires library `image-file.el'")) + (diredp-ensure-mode) + (list current-prefix-arg))) + (dired-map-over-marks-check #'diredp-display-image arg 'display\ image + (diredp-fewer-than-2-files-p arg))) + +(defun diredp-display-image () + "Display image file at point. Log an error using `dired-log'." + (let ((file (dired-get-filename 'LOCAL 'NO-ERROR)) + (failure nil)) + (save-excursion + (if (let ((inhibit-changing-match-data t)) + (and file (diredp-string-match-p (image-file-name-regexp) file))) + (condition-case err + (let ((find-file-run-dired nil)) (find-file-other-window file)) + (error (setq failure (error-message-string err)))) + (dired-log (format "Not an image file: `%s'" file)) + (setq failure t))) + (and failure ; Return nil for success. + (prog1 file ; Return file name for failure. + (unless (eq t failure) (dired-log "Cannot display image file `%s':\n%s\n" file failure) t))))) + +;;;###autoload +(defun diredp-image-show-this-file (&optional arg) + "Show the image file named on this line in another frame or window. +Option `diredp-image-show-this-file-use-frame-flag' which is used. + +With a prefix arg, shrink the image to fit a frame that many lines +high or a window at least that many lines high. +Otherwise, show the image full size. +Note: + * To show the image full size, you can also use `\\<dired-mode-map>\\[dired-find-file]'. + * To show the image in another window, at whatever scale fits there, + you can use `\\[image-dired-dired-display-image]'." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (image-dired-create-display-image-buffer) + (let ((fit-frame-inhibit-fitting-flag t) ; In `fit-frame.el'. + (img-file (diredp-get-image-filename))) + (if img-file + (with-current-buffer image-dired-display-image-buffer + (let* ((window-min-height (if arg + (prefix-numeric-value arg) + (ceiling (cdr (image-size (create-image img-file)))))) + (special-display-frame-alist (if diredp-image-show-this-file-use-frame-flag + (cons `(height . ,window-min-height) + special-display-frame-alist) + special-display-frame-alist)) + (special-display-buffer-names (if diredp-image-show-this-file-use-frame-flag + (cons image-dired-display-image-buffer + special-display-buffer-names) + special-display-buffer-names))) + (display-buffer image-dired-display-image-buffer) + (image-dired-display-image img-file (not arg)))) + (message "No image file here")))) ; An error is handled by `diredp-get-image-filename'. + +(defun diredp-report-file-result (file result failure echop) + (cond (failure + (when echop (message "Error for %s:\n%s\n" file failure) (sit-for 1)) + (dired-log "Error for %s:\n%s\n" file failure) + (dired-make-relative file)) ; Return file name for failure. + (t + (when echop (message "Result for %s:\n%s\n" file result) (sit-for 1)) + (dired-log "Result for %s:\n%s\n" file result) + nil))) ; Return nil for success. + +;;;###autoload +(defun diredp-do-emacs-command (command &optional arg) + "Invoke an Emacs COMMAND in each marked file. +Visit each marked file at its beginning, then invoke COMMAND. +You are prompted for the COMMAND. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily. + +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (diredp-ensure-mode) + (list (diredp-read-command) current-prefix-arg))) + (save-selected-window + (diredp-map-over-marks-and-report + #'diredp-invoke-emacs-command arg 'invoke\ emacs\ command (diredp-fewer-than-2-files-p arg) + command (diredp-fewer-than-echo-limit-files-p arg)))) + +(defun diredp-invoke-emacs-command (command &optional echop) + "Visit file of this line at its beginning, then invoke COMMAND. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (call-interactively command)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + +(defun diredp-read-command (&optional prompt default) + "Read the name of a command and return a symbol with that name. +\(A command is anything that satisfies predicate `commandp'.) +Prompt with PROMPT, which defaults to \"Command: \". +By default, return the command named DEFAULT (or, with Emacs 23+, its +first element if DEFAULT is a list). (If DEFAULT does not name a +command then it is ignored.)" + (setq prompt (or prompt "Command: ")) + (let ((name (completing-read prompt obarray #'commandp t nil + 'extended-command-history default))) + (while (string= "" name) + (setq name (completing-read prompt obarray #'commandp t nil + 'extended-command-history default))) + (intern name))) + +(when (fboundp 'diredp-read-expression) ; Emacs 22+ + + (defun diredp-do-lisp-sexp (sexp &optional arg) + "Evaluate an Emacs-Lisp SEXP in each marked file. +Visit each marked file at its beginning, then evaluate SEXP. +You are prompted for the SEXP. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily. + +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (diredp-ensure-mode) + (list (diredp-read-expression "Sexp: ") current-prefix-arg))) + (save-selected-window + (diredp-map-over-marks-and-report + #'diredp-eval-lisp-sexp arg 'eval\ elisp\ sexp (diredp-fewer-than-2-files-p arg) + sexp (diredp-fewer-than-echo-limit-files-p arg)))) + + (defun diredp-eval-lisp-sexp (sexp &optional echop) + "Visit file of this line at its beginning, then evaluate SEXP. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (eval-expression sexp)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + + ) + +;;; Face Definitions + +(defface diredp-autofile-name + '((((background dark)) (:background "#111313F03181")) ; Very dark blue + (t (:background "#EEECEC0FCE7E"))) ; Very pale goldenrod + "*Face used in Dired for names of files that are autofile bookmarks." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-autofile-name 'diredp-autofile-name) + +(defface diredp-compressed-file-name + '((((background dark)) (:foreground "Blue")) + (t (:foreground "Brown"))) + "*Face used for compressed file names." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-compressed-file-name 'diredp-compressed-file-name) + +(defface diredp-compressed-file-suffix + '((((background dark)) (:foreground "Blue")) + (t (:foreground "Yellow"))) + "*Face used for compressed file suffixes in Dired buffers. +This means the `.' plus the file extension. Example: `.zip'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-compressed-file-suffix 'diredp-compressed-file-suffix) + +(defface diredp-date-time + '((((background dark)) (:foreground "#74749A9AF7F7")) ; ~ med blue + (t (:foreground "DarkGoldenrod4"))) + "*Face used for date and time in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-date-time 'diredp-date-time) + +(defface diredp-deletion + '((t (:foreground "Yellow" :background "Red"))) + "*Face used for deletion flags (D) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-deletion 'diredp-deletion) + +(defface diredp-deletion-file-name + '((t (:foreground "Red"))) + "*Face used for names of deleted files in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-deletion-file-name 'diredp-deletion-file-name) + +(defface diredp-dir-heading + '((((background dark)) (:foreground "Yellow" :background "#00003F3F3434")) ; ~ dark green + (t (:foreground "Blue" :background "Pink"))) + "*Face used for directory headings in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-heading 'diredp-dir-heading) + +(defface diredp-dir-name + '((((background dark)) + (:foreground "#7474FFFFFFFF" :background "#2C2C2C2C2C2C")) ; ~ cyan, dark gray + (t (:foreground "DarkRed" :background "LightGray"))) + "*Face used for directory names." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-name 'diredp-dir-name) + +(defface diredp-dir-priv + '((((background dark)) + (:foreground "#7474FFFFFFFF" :background "#2C2C2C2C2C2C")) ; ~ cyan, dark gray + (t (:foreground "DarkRed" :background "LightGray"))) + "*Face used for directory privilege indicator (d) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-priv 'diredp-dir-priv) + +(defface diredp-exec-priv + '((((background dark)) (:background "#4F4F3B3B2121")) ; ~ dark brown + (t (:background "LightSteelBlue"))) + "*Face used for execute privilege indicator (x) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-exec-priv 'diredp-exec-priv) + +;; For this to show up, you need `F' among the options in `dired-listing-switches'. +;; For example, I use "-alF" for `dired-listing-switches'. +(defface diredp-executable-tag + '((t (:foreground "Red"))) + "*Face used for executable tag (*) on file names in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-executable-tag 'diredp-executable-tag) + +(defface diredp-file-name + '((((background dark)) (:foreground "Yellow")) + (t (:foreground "Blue"))) + "*Face used for file names (without suffixes) in Dired buffers. +This means the base name. It does not include the `.'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-file-name 'diredp-file-name) + +(defface diredp-file-suffix + '((((background dark)) (:foreground "#7474FFFF7474")) ; ~ light green + (t (:foreground "DarkMagenta"))) + "*Face used for file suffixes in Dired buffers. +This means the `.' plus the file extension. Example: `.elc'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-file-suffix 'diredp-file-suffix) + +(defface diredp-flag-mark + '((((background dark)) (:foreground "Blue" :background "#7575D4D41D1D")) ; ~ olive green + (t (:foreground "Yellow" :background "Blueviolet"))) + "*Face used for flags and marks (except D) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-flag-mark 'diredp-flag-mark) + +(defface diredp-flag-mark-line + '((((background dark)) (:background "#787831311414")) ; ~ dark red brown + (t (:background "Skyblue"))) + "*Face used for flagged and marked lines in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-flag-mark-line 'diredp-flag-mark-line) + +(defface diredp-ignored-file-name + '((((background dark)) (:foreground "#C29D6F156F15")) ; ~ salmon + (t (:foreground "#00006DE06DE0"))) ; ~ dark cyan + "*Face used for files whose names are omitted based on the extension. +See also face `diredp-omit-file-name'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-ignored-file-name 'diredp-ignored-file-name) + +(defface diredp-link-priv + '((((background dark)) (:foreground "#00007373FFFF")) ; ~ blue + (t (:foreground "DarkOrange"))) + "*Face used for link privilege indicator (l) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-link-priv 'diredp-link-priv) + +(when (> emacs-major-version 21) + (defface diredp-mode-line-marked + '((t (:foreground "DarkViolet"))) + "*Face for marked number in mode-line `mode-name' for Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) + + (defface diredp-mode-line-flagged + '((t (:foreground "Red"))) + "*Face for flagged number in mode-line `mode-name' for Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces)) + +(defface diredp-no-priv + '((((background dark)) (:background "#2C2C2C2C2C2C")) ; ~ dark gray + (t (:background "LightGray"))) + "*Face used for no privilege indicator (-) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-no-priv 'diredp-no-priv) + +(defface diredp-number + '((((background dark)) (:foreground "#FFFFFFFF7474")) ; ~ light yellow + (t (:foreground "DarkBlue"))) + "*Face used for numerical fields in Dired buffers. +In particular, inode number, number of hard links, and file size." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-number 'diredp-number) + +(defface diredp-omit-file-name + (if (assq :inherit custom-face-attributes) ; Emacs 22+ + '((((background dark)) (:inherit diredp-ignored-file-name :strike-through "#555555555555")) ; ~ dark gray + (t (:inherit diredp-ignored-file-name :strike-through "#AAAAAAAAAAAA"))) ; ~ light gray + '((((background dark)) (:foreground "#C29D6F156F15")) ; ~ salmon + (t (:foreground "#00006DE06DE0")))) ; ~ dark cyan + "*Face used for files whose names will be omitted in `dired-omit-mode'. +This means file names that match regexp `diredp-omit-files-regexp'. +\(File names matching `dired-omit-extensions' are highlighted with face +`diredp-ignored-file-name' instead.)" + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-omit-file-name 'diredp-omit-file-name) + +(defface diredp-other-priv + '((((background dark)) (:background "#111117175555")) ; ~ dark blue + (t (:background "PaleGoldenrod"))) + "*Face used for l,s,S,t,T privilege indicators in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-other-priv 'diredp-other-priv) + +(defface diredp-rare-priv + '((((background dark)) (:foreground "Green" :background "#FFFF00008080")) ; ~ hot pink + (t (:foreground "Magenta" :background "SpringGreen"))) + "*Face used for rare privilege indicators (b,c,s,m,p,S) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-rare-priv 'diredp-rare-priv) + +(defface diredp-read-priv + '((((background dark)) (:background "#999932325555")) ; ~ burgundy / dark magenta + (t (:background "MediumAquamarine"))) + "*Face used for read privilege indicator (w) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-read-priv 'diredp-read-priv) + +(defface diredp-symlink + '((((background dark)) (:foreground "#00007373FFFF")) ; ~ blue + (t (:foreground "DarkOrange"))) + "*Face used for symbolic links in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-symlink 'diredp-symlink) + +(defface diredp-tagged-autofile-name + '((((background dark)) (:background "#328C0411328C")) ; Very dark magenta + (t (:background "#CD73FBEECD73"))) ; Very pale green + "*Face used in Dired for names of files that are autofile bookmarks." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-tagged-autofile-name 'diredp-tagged-autofile-name) + +(defface diredp-write-priv + '((((background dark)) (:background "#25258F8F2929")) ; ~ dark green + (t (:background "Orchid"))) + "*Face used for write privilege indicator (w) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-write-priv 'diredp-write-priv) + +;; Fix Emacs 20 recognition of fields up through file name when size is expressed using `k' etc. +(when (and (< emacs-major-version 21) (not (boundp 'diredp-loaded-p)) + dired-move-to-filename-regexp ; These last two checks are just in case. + (eq (aref dired-move-to-filename-regexp 7) ?\ )) + (setq dired-move-to-filename-regexp (concat "[0-9][BkKMGTPEZY]?" + (substring dired-move-to-filename-regexp 7)))) + +;;; Define second level of fontifying. +(defvar diredp-font-lock-keywords-1 + (list + '("^ \\(.+:\\)$" 1 diredp-dir-heading) ; Directory headers + '("^ wildcard.*$" 0 'default) ; Override others, e.g. `l' for `diredp-other-priv'. + '("^ (No match).*$" 0 'default) ; Override others, e.g. `t' for `diredp-other-priv'. + '("[^ .]\\(\\.[^. /]+\\)$" 1 diredp-file-suffix) ; Suffix, including `.'. + '("\\([^ ]+\\) -> .+$" 1 diredp-symlink) ; Symbolic links + + ;; 1) Date/time and 2) filename w/o suffix. + ;; This is a bear, and it is fragile - Emacs can change `dired-move-to-filename-regexp'. + (if (or (not (fboundp 'version<)) (version< emacs-version "23.2")) + (list dired-move-to-filename-regexp + (list 1 'diredp-date-time t t) ; Date/time + (list (concat "\\(.+\\)\\(" (concat (funcall #'regexp-opt diredp-compressed-extensions) + "\\)[*]?$")) ; Compressed-file name + nil nil (list 0 diredp-compressed-file-name 'keep t))) + `(,dired-move-to-filename-regexp + (7 diredp-date-time t t) ; Date/time, locale (western or eastern) + (2 diredp-date-time t t) ; Date/time, ISO + (,(concat "\\(.+\\)\\(" (concat (funcall #'regexp-opt diredp-compressed-extensions) + "\\)[*]?$")) + nil nil (0 diredp-compressed-file-name keep t)))) ; Compressed-file suffix + (if (or (not (fboundp 'version<)) (version< emacs-version "23.2")) + (list dired-move-to-filename-regexp + (list 1 'diredp-date-time t t) ; Date/time + (list "\\(.+\\)$" nil nil (list 0 diredp-file-name 'keep t))) ; Filename + `(,dired-move-to-filename-regexp + (7 diredp-date-time t t) ; Date/time, locale (western or eastern) + (2 diredp-date-time t t) ; Date/time, ISO + ("\\(.+\\)$" nil nil (0 diredp-file-name keep t)))) ; Filename (not a compressed file) + + ;; Files to ignore. + ;; Use face `diredp-ignored-file-name' for omission by file-name extension. + ;; Use face `diredp-omit-file-name' for omission by entire file name. + (let* ((omit-exts (or (and (boundp 'dired-omit-extensions) dired-omit-extensions) + completion-ignored-extensions)) + (omit-exts (and omit-exts (mapconcat #'regexp-quote omit-exts "\\|"))) + (compr-exts (and diredp-ignore-compressed-flag + (concat "\\|" (mapconcat #'regexp-quote diredp-compressed-extensions "\\|"))))) + (list (concat "^ \\(.*\\(" omit-exts compr-exts "\\)[*]?\\)$") ; [*]? allows for executable flag (*). + 1 diredp-ignored-file-name t)) + `(,(concat "^.*" dired-move-to-filename-regexp + "\\(" diredp-omit-files-regexp "\\).*[*]?$") ; [*]? allows for executable flag (*). + (0 diredp-omit-file-name t)) + + ;; Compressed-file (suffix) + (list (concat "\\(" (funcall #'regexp-opt diredp-compressed-extensions) "\\)[*]?$") + 1 diredp-compressed-file-suffix t) + '("\\([*]\\)$" 1 diredp-executable-tag t) ; Executable (*) + + ;; Inode, hard-links, & file size (. and , are for the decimal point, depending on locale) + ;; See comment for `directory-listing-before-filename-regexp' in `files.el' or `files+.el'. + '("\\(\\([0-9]+\\([.,][0-9]+\\)?\\)[BkKMGTPEZY]?[ /]?\\)" 1 diredp-number) + + ;; Directory names - exclude d:/..., Windows drive letter in a dir heading. + (list (concat dired-re-maybe-mark dired-re-inode-size "\\(d\\)[^:]") + '(1 diredp-dir-priv t) '(".+" (dired-move-to-filename) nil (0 diredp-dir-name t))) + + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\(x\\)") ; o x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\([lsStT]\\)") ; o misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].......\\(w\\).") ; o w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]......\\(r\\)..") ; o r + '(1 diredp-read-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\(x\\)...") ; g x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\([lsStT]\\)...") ; g misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]....\\(w\\)....") ; g w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]...\\(r\\).....") ; g r + '(1 diredp-read-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\(x\\)...") ; u x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\([lsStT]\\)...") ; u misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].\\(w\\)....") ; u w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]\\(r\\).....") ; u r + '(1 diredp-read-priv)) + + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\([-rwxlsStT]\\)") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].......\\([-rwxlsStT]\\).") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]......\\([-rwxlsStT]\\)..") ; u - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\([-rwxlsStT]\\)...") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]....\\([-rwxlsStT]\\)....") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]...\\([-rwxlsStT]\\).....") ; u - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\([-rwxlsStT]\\)......") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].\\([-rwxlsStT]\\).......") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]\\([-rwxlsStT]\\)........") ; u - + '(1 diredp-no-priv keep)) + + (list (concat dired-re-maybe-mark dired-re-inode-size "\\([bcsmpS]\\)") ; (rare) + '(1 diredp-rare-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "\\(l\\)[-rwxlsStT]") ; l + '(1 diredp-rare-priv keep)) + + (list (concat "^\\([^\n " (char-to-string dired-del-marker) "].*$\\)") + 1 diredp-flag-mark-line t) ; Flag/mark lines + (list (concat "^\\([^\n " (char-to-string dired-del-marker) "]\\)") ; Flags, marks (except D) + 1 diredp-flag-mark t) + + (list (concat "^\\([" (char-to-string dired-del-marker) "].*$\\)") ; Deletion-flagged lines + 1 diredp-deletion-file-name t) + (list (concat "^\\([" (char-to-string dired-del-marker) "]\\)") ; Deletion flags (D) + 1 diredp-deletion t) + + ) "2nd level of Dired highlighting. See `font-lock-maximum-decoration'.") + + +(defun diredp--set-up-font-locking () + "Add this to `dired-mode-hook' to provide for second-level fontifying." + (set (make-local-variable 'font-lock-defaults) + ;; Two levels. Use 3-element list, since it is standard to have one more + ;; than the number of levels. This is necessary for it to work with + ;; `font(-lock)-menus.el'. + '((dired-font-lock-keywords + dired-font-lock-keywords + diredp-font-lock-keywords-1) + t nil nil beginning-of-line)) + ;; Refresh `font-lock-keywords' from `font-lock-defaults' + (when (fboundp 'font-lock-refresh-defaults) (font-lock-refresh-defaults))) + +;;; Provide for the second level of fontifying. +(add-hook 'dired-mode-hook 'diredp--set-up-font-locking) + +;; Ensure that Dired buffers are refontified when you use `g' or otherwise read in the file list. +(defun diredp-refontify-buffer () + "Turn `font-lock-mode' off, then on." + (setq font-lock-mode nil) + (font-lock-mode)) +(add-hook 'dired-after-readin-hook 'diredp-refontify-buffer) + +;;; Function Definitions + +;;; $$$$$$$$ +;;; (defun diredp-dired-files (arg &optional switches) ; Not bound +;;; "Like `dired', but non-positive prefix arg prompts for files to list. +;;; This is like `dired' unless you use a non-positive prefix arg. +;;; In that case, you are prompted for names of files and directories to +;;; list, and then you are prompted for the name of the Dired buffer that +;;; lists them. Use `C-g' when you are done entering file names to list. + +;;; In all cases, when inputting a file or directory name you can use +;;; shell wildcards. + +;;; If you use Icicles, then in Icicle mode the following keys are bound +;;; in the minibuffer during completion (`*' means the key requires +;;; library `Bookmark+'): + +;;; M-| - Open Dired on the file names matching your input +;;; C-c + - Create a new directory +;;; *C-x a + - Add tags to the current-candidate file +;;; *C-x a - - Remove tags from the current-candidate file +;;; *C-x m - Access file bookmarks (not just autofiles)" +;;; (interactive (diredp-dired-files-interactive-spec "")) +;;; (when (consp arg) +;;; (let ((buf (dired-find-buffer-nocreate (car arg)))) ; Respect file list. +;;; (when buf (kill-buffer buf)))) +;;; (if (fboundp 'pop-to-buffer-same-window) +;;; (pop-to-buffer-same-window (dired-noselect arg switches)) +;;; (switch-to-buffer (dired-noselect arg switches)))) + +;;; (defun diredp-dired-files-other-window (arg &optional switches) ; Not bound +;;; "Same as `diredp-dired-files' except uses another window." +;;; (interactive (diredp-dired-files-interactive-spec "in other window ")) +;;; (when (consp arg) +;;; (let ((buf (dired-find-buffer-nocreate (car arg)))) ; Respect file list. +;;; (when buf (kill-buffer buf)))) +;;; (dired-other-window arg switches)) + +;;;###autoload +(defun diredp-dired-for-files (arg &optional switches) ; Bound to `C-x D F' + "Dired file names that you enter, in a Dired buffer that you name. +You are prompted for the name of the Dired buffer to use. +You are then prompted for names of files and directories to list, + which can be located anywhere. +Use `C-g' when you are done. + +With a prefix arg you are first prompted for the `ls' switches to use. + +See also `dired' (including the advice)." + (interactive (let ((current-prefix-arg (if current-prefix-arg 0 -1))) + (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + (dired arg switches)) + +;;;###autoload +(defun diredp-dired-for-files-other-window (arg &optional switches) ; Bound to `C-x 4 D F' + "Same as `diredp-dired-for-files', except uses another window." + (interactive (let ((current-prefix-arg (if current-prefix-arg 0 -1))) + (dired-read-dir-and-switches "in other window " 'READ-EXTRA-FILES-P))) + (dired-other-window arg switches)) + +;;;###autoload +(defun diredp-dired-recent-dirs (buffer &optional arg) ; Bound to `C-x D R' + "Open Dired in BUFFER, showing recently used directories. +You are prompted for BUFFER. + +No prefix arg or a plain prefix arg (`C-u', `C-u C-u', etc.) means +list all of the recently used directories. + +With a prefix arg: +* If 0, `-', or plain (`C-u') then you are prompted for the `ls' + switches to use. +* If not plain (`C-u') then: + * If >= 0 then the directories to include are read, one by one. + * If < 0 then the directories to exclude are read, one by one. + +When entering directories to include or exclude, use `C-g' to end." + (interactive (list (completing-read "Dired buffer name: " dired-buffers) current-prefix-arg)) + (unless (require 'recentf nil t) (error "This command requires library `recentf.el'")) + (let ((switches (and (or (zerop (prefix-numeric-value arg)) (consp arg)) + (read-string "Dired listing switches: " dired-listing-switches)))) + (dired (cons (generate-new-buffer-name buffer) (diredp-recent-dirs arg)) switches))) + +;;;###autoload +(defun diredp-dired-recent-dirs-other-window (buffer &optional arg) ; Bound to `C-x 4 D R' + "Same as `diredp-dired-recent-dirs', but use other window." + (interactive (list (completing-read "Dired buffer name: " dired-buffers) current-prefix-arg)) + (unless (require 'recentf nil t) (error "This command requires library `recentf.el'")) + (let ((switches (and (or (zerop (prefix-numeric-value arg)) (consp arg) (eq '- arg)) + (read-string "Dired listing switches: " dired-listing-switches)))) + (dired-other-window (cons (generate-new-buffer-name buffer) (diredp-recent-dirs arg)) switches))) + +(defun diredp-recent-dirs (arg) + "Return a list of recently used directories. +ARG is as for `diredp-dired-recent-dirs'." + (let ((recent-dirs (diredp-remove-if #'diredp-root-directory-p + (diredp-delete-dups + (mapcar (lambda (f/d) + (if (file-directory-p f/d) f/d (file-name-directory f/d))) + recentf-list))))) + (if (and arg (atom arg)) + (diredp-read-include/exclude 'Dir recent-dirs (not (natnump (prefix-numeric-value arg)))) + recent-dirs))) + +(defun diredp-read-include/exclude (thing things &optional exclude) + "Read which THINGs to include (or to EXCLUDE, if non-nil) from list THINGS. +The things are read one by one. `C-g' stops reading. + +THING is a string or symbol naming the type of thing to read, e.g., +`File' or `Directory'. It is used only in the prompt, which is THING +followed by \" to exclude\" or \" to include\" and a reminder about `C-g'. + +A new list is returned - list THINGS is not modified." + (let* ((thgs (if exclude (copy-sequence things) ())) + (prompt (format "%s to %s (C-g when done): " thing (if exclude 'EXCLUDE 'INCLUDE))) + (completion-ignore-case (or (and (boundp 'read-file-name-completion-ignore-case) + (memq thing '(Dir Directory File "Dir" "Directory" "File")) ; Hack + read-file-name-completion-ignore-case) + completion-ignore-case)) + thing) + (while (condition-case nil + (setq thing (completing-read prompt (mapcar #'list things) nil t)) + (quit nil)) + (if exclude (delete thing thgs) + (push thing thgs))) + thgs)) + +;;; $$$$$$$$ +;;; (defun diredp-dired-files-interactive-spec (str) +;;; "`interactive' spec for `diredp-dired-files' commands. +;;; STR is a string appended to the prompt. +;;; With non-negative prefix arg, read switches. +;;; With non-positive prefix arg, read files and dirs to list and then the +;;; Dired buffer name. User uses `C-g' when done reading files and dirs. + +;;; If you use Icicles, then in Icicle mode the following keys are bound +;;; in the minibuffer during completion (`*' means the key requires +;;; library `Bookmark+'): + +;;; M-| - Open Dired on the file names matching your input +;;; C-c + - Create a new directory +;;; *C-x a + - Add tags to the current-candidate file +;;; *C-x a - - Remove tags from the current-candidate file +;;; *C-x m - Access file bookmarks (not just autofiles)" +;;; (list +;;; (unwind-protect +;;; (let ((icicle-sort-comparer (or (and (boundp 'icicle-file-sort) ;; If not reading files +;;; icicle-file-sort) ;; then dirs first. +;;; (and (> (prefix-numeric-value current-prefix-arg) 0) +;;; 'icicle-dirs-first-p) +;;; icicle-sort-comparer)) +;;; (icicle-all-candidates-list-alt-action-fn ; M-|' +;;; (lambda (files) +;;; (let ((enable-recursive-minibuffers t)) +;;; (dired-other-window (cons (read-string "Dired buffer name: ") files)))))) +;;; (when (fboundp 'icicle-bind-file-candidate-keys) (icicle-bind-file-candidate-keys)) +;;; (if (> (prefix-numeric-value current-prefix-arg) 0) +;;; ;; If a dialog box is about to be used, call `read-directory-name' so the dialog +;;; ;; code knows we want directories. Some dialog boxes can only select directories +;;; ;; or files when popped up, not both. +;;; (if (and (fboundp 'read-directory-name) (next-read-file-uses-dialog-p)) +;;; (read-directory-name (format "Dired %s(directory): " str) nil +;;; default-directory nil) +;;; (read-file-name (format "Dired %s(directory): " str) nil default-directory nil)) +;;; (let ((insert-default-directory nil) +;;; (files ()) +;;; file) +;;; (while (condition-case nil ; Use lax completion, to allow wildcards. +;;; (setq file (read-file-name "File or dir (C-g when done): ")) +;;; (quit nil)) +;;; (push file files)) +;;; (cons (read-string "Dired buffer name: " nil nil default-directory) files)))) +;;; (when (fboundp 'icicle-unbind-file-candidate-keys) +;;; (icicle-unbind-file-candidate-keys))) +;;; (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) +;;; (read-string "Dired listing switches: " dired-listing-switches)))) + +;;;###autoload +(defun diredp-dired-union (dired-name dirbufs &optional switches extra) ; Bound to `C-x D U' + "Create a Dired buffer that is the union of some existing Dired buffers. +With a non-negative prefix arg, you are prompted for `ls' switches. +With a non-positive prefix arg, you are prompted for file and dir +names to add to the listing - see below. + +You are prompted for the name of the Dired union buffer. Completion +against names of existing Dired buffers is available, but you can +enter any other name to create a new Dired buffer of that name. + +If the union buffer name you choose names an existing Dired buffer, +then what happens depends on whether that buffer is an ordinary Dired +directory listing or a list of arbitrary file names. That is, it +depends on whether `dired-directory' is a directory name or a cons of +a Dired buffer name plus file names. + +* If the buffer is an ordinary Dired listing, then it is converted to + an explicit list of absolute file names, just as if these had been + chosen individually. The existing buffer and window are replaced by + new ones that show the explicit listing. (This replacement is + necessary because the list of files contained in an ordinary Dired + listing cannot be modified.) + +* If the buffer lists arbitrary file names explicitly, then it is + updated to include also the files from any Dired buffers and any + additional files that you specify. + +If the union buffer name you choose does not name an existing Dired +buffer, then its `default-directory' is the same as the +`default-directory' before invoking the command. + +If you use a non-positive prefix arg, then you can next choose +additional file and directory names to add to the listing. Use `C-g' +when done choosing them. + +Any directory names you choose this way are included as single entries +in the listing - the directory contents are not included (these +directories are not unioned). To instead include the contents of a +directory chosen this way, use a glob pattern: `/*' after the +directory name. + +You are then prompted for the Dired buffers to union. Use `C-g' when +done choosing them. These Dired listings to union are included in the +order that you chose them, and each entry is listed only once in the +new Dired buffer. + +The new Dired listing respects the markings, subdirectory insertions, +and hidden subdirectories of the selected Dired listings. However, in +case of conflict between marked or unmarked status for the same entry, +the entry is marked. Similarly, in case of conflict over an included +subdirectory between it being hidden or shown, it is hidden, but its +contained files are also listed. + +See also command `diredp-add-to-dired-buffer'. + +From Lisp: + DIRED-NAME is the name of the resulting Dired union buffer. + DIRBUFS is a list of the names of Dired buffers to union. + SWITCHES is a string of `ls' switches. + EXTRA is a list of files & directories to be included in the listing." + (interactive (diredp-dired-union-interactive-spec "UNION " + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0)))) + (diredp-dired-union-1 dired-name dirbufs switches extra)) + +;;;###autoload +(defun diredp-dired-union-other-window (dired-name dirbufs &optional switches extra) ; Bound to `C-x 4 D U' + "Same as `diredp-dired-union', except use other window." + (interactive (diredp-dired-union-interactive-spec "UNION " + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0)))) + (diredp-dired-union-1 dired-name dirbufs switches extra 'OTHERWIN)) + +;;;###autoload +(defun diredp-add-to-dired-buffer (dired-name to-add &optional switches) ; Bound to `C-x D A' + "Add individual file and directory names to a Dired buffer. +You are prompted for the buffer name. +With a prefix arg, you are also prompted for the `ls' switches. + +The buffer must either not exist yet or must list arbitrary file and +directory names. That is, it cannot be an ordinary Dired directory +listing - those cannot be modified. + +Any directory names you choose this way are included as single entries +in the listing - the directory contents are not included (these +directories are not unioned). To instead include the contents of a +directory chosen this way, use a glob pattern: `/*' after the +directory name. + +See also command `diredp-dired-union'. + +From Lisp: + DIRED-NAME is the name of the Dired buffer to modify. + TO-ADD is the list of files and dirs to add to it. + SWITCHES is the string of `ls' switches." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P))) + (list (nth 0 all) (nth 3 all) (nth 2 all)))) + (diredp-dired-union-1 dired-name () switches to-add)) + +;;;###autoload +(defun diredp-add-to-dired-buffer-other-window (dired-name to-add &optional switches) ; Bound to `C-x 4 D A' + "Same as `diredp-add-to-dired-buffer', except use other window." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P))) + (list (nth 0 all) (nth 3 all) (nth 2 all)))) + (diredp-dired-union-1 dired-name () switches to-add 'OTHERWIN)) + +;;;###autoload +(defun diredp-add-to-this-dired-buffer (dired-name to-add &optional switches) ; Not bound by default + "Same as `diredp-add-to-dired-buffer' for this Dired buffer." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (progn (unless (derived-mode-p 'dired-mode) (error "Not in a Dired buffer")) + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs here " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P + (buffer-name)))) + (list (nth 0 all) (nth 3 all) (nth 2 all))))) + (diredp-dired-union-1 dired-name () switches to-add)) + +;; $$$$$ Maybe I should set `dired-sort-inhibit' to t for now (?), +;; since there is an Emacs bug (at least on Windows) that prevents +;; sorting from working for a Dired buffer with an explicit file list. +(defun diredp-dired-union-1 (dired-name dirbufs switches extra &optional otherwin) + "Helper for `diredp-dired-union' and `diredp-add-to-dired-buffer'. +Non-nil optional OTHERWIN means use other window for the Dired buffer. +See `diredp-dired-union' for the other argument descriptions." + (let ((dbuf (get-buffer dired-name)) + (files extra) + (marked ()) + (subdirs ()) + (hidden-dirs ()) + hid-here files-here) + (dolist (buf (reverse dirbufs)) + (with-current-buffer buf + (unwind-protect + (progn (setq hid-here (save-excursion (dired-remember-hidden)) + files-here (if (consp dired-directory) + (reverse (cdr dired-directory)) ; Reverse bc will push. + ())) + (unless files-here + (save-excursion ; This bit is more or less from `dired-toggle-marks'. + (goto-char (point-min)) + (while (not (eobp)) + (or (diredp-looking-at-p dired-re-dot) + (push (dired-get-filename nil 'NO-ERROR-P) files-here)) + (forward-line 1))) + (setq files-here (delq nil files-here))) + (dolist (hid-here hid-here) (push hid-here hidden-dirs)) + (dolist (sub (cdr (reverse dired-subdir-alist))) + (push (list (car sub)) subdirs)) + (dolist (mkd (dired-remember-marks (point-min) (point-max))) ; This unhides. + (push (car mkd) marked)) + (dolist (file files-here) + (when (or (not (file-name-absolute-p file)) (not (member file files))) + (push file files)))) + (save-excursion ; Hide subdirs that were hidden. + (dolist (dir hid-here) (when (dired-goto-subdir dir) (dired-hide-subdir 1))))))) + ;; For an existing Dired buffer having this name whose `dired-directory' is a cons: + ;; 1. Include the files and dirs already listed there. + ;; 2. Kill the current buffer and delete its window. A new buffer of the same name is created and shown. + (when dbuf + (with-current-buffer dbuf + (when (consp dired-directory) (setq files (diredp-set-union (cdr dired-directory) files))) + (let ((win (get-buffer-window dbuf 0))) (when win (delete-window win))) + (kill-buffer dbuf))) + (setq dbuf (dired-other-window (cons dired-name files) switches)) + (with-current-buffer dbuf + (let ((inhibit-read-only t)) + (dired-insert-old-subdirs subdirs) + (dired-mark-remembered ; Don't really need `expand-file-name' - already abs. + (mapcar (lambda (mf) (cons (expand-file-name mf dired-directory) 42)) marked)) + (save-excursion + (dolist (hdir hidden-dirs) (when (dired-goto-subdir hdir) (dired-hide-subdir 1)))))))) + +(defun diredp-dired-union-interactive-spec (string &optional no-dired-bufs read-extra-files-p dired-buffer) + "Read arguments for `diredp-dired-union' and `diredp-add-to-dired-buffer'. +STRING is appended to the prompt for the listing buffer name. +Non-nil NO-DIRED-BUFS means do not read Dired buffers to union. +Non-nil READ-EXTRA-FILES-P is passed to `dired-read-dir-and-switches', + and means read extra files to add to the listing. +Non-nil DIRED-BUFFER is passed to `dired-read-dir-and-switches'. + It is the name of the Dired union buffer." + (let* ((current-prefix-arg -1) + (dir+switches (dired-read-dir-and-switches string read-extra-files-p dired-buffer)) + (dirname (car dir+switches)) + (switches (cadr dir+switches)) + (dirbufs ()) + (bufs ()) + (extra-files ()) + buf) + (when (consp dirname) (setq extra-files (cdr dirname) + dirname (car dirname))) + (unless no-dired-bufs + ;; Remove any killed buffers from `dired-buffers'. Then use all but the target buffer as candidates. + (dolist (db dired-buffers) + (if (buffer-live-p (cdr db)) + (unless (equal dirname (buffer-name (cdr db))) + (push (cons (buffer-name (cdr db)) (car db)) dirbufs)) + (setq dired-buffers (delq db dired-buffers)))) + (while (and dirbufs (condition-case nil + (setq buf (completing-read "Existing Dired buffer to include (C-g when done): " + dirbufs nil t nil 'buffer-name-history + (and dirbufs (car (assoc (buffer-name) dirbufs))))) + (quit nil))) + (push buf bufs) + (setq dirbufs (delete (cons buf (with-current-buffer buf (expand-file-name default-directory))) + dirbufs))) + (setq bufs (nreverse bufs))) + (list dirname bufs switches extra-files))) + +(when (> emacs-major-version 23) ; `compilation--loc->file-struct' + + (defalias 'diredp-grepped-files-other-window 'diredp-compilation-files-other-window) + (defun diredp-compilation-files-other-window (&optional switches) + "Open Dired on the files indicated by compilation (e.g., `grep') hits. +Applies to any `compilation-mode'-derived buffer, such as `*grep*'. +You are prompted for the name of the new Dired buffer. +With a prefix arg you are first prompted for the `ls' switches. + +\(However, Emacs bug #20739 means that the switches are ignored.)" + (interactive (list (and current-prefix-arg (read-string "Dired listing switches: " dired-listing-switches)))) + (unless (compilation-buffer-p (current-buffer)) (error "Not in a buffer derived from `compilation-mode'")) + (let ((files ())) + (save-excursion (goto-char (point-min)) + (while (condition-case nil (compilation-next-file 1) (error nil)) + (setq compilation-current-error (point)) + (push (diredp-file-for-compilation-hit-at-point) files))) + (setq files (nreverse files)) + (dired-other-window + (cons (read-string "Dired buffer name: " nil nil (generate-new-buffer-name default-directory)) files) + switches))) + + (defun diredp-file-for-compilation-hit-at-point () + "Return the name of the file for the compilation hit at point. +The name is expanded in the directory for the last directory change." + (let* ((msg (compilation-next-error 0)) + (loc (compilation--message->loc msg)) + (filestruct (compilation--loc->file-struct loc)) + (file (caar filestruct)) + (dir (cadr (car filestruct)))) + (when dir (setq file (expand-file-name file dir))) + file)) + ) + +;;;###autoload +(defun diredp-fileset (flset-name) ; Bound to `C-x D S' + "Open Dired on the files in fileset FLSET-NAME." + (interactive + (progn (unless (require 'filesets nil t) (error "Feature `filesets' not provided")) + (unless filesets-data (error "`filesets-data' is empty")) + (list (completing-read "Open Dired on fileset: " filesets-data)))) + (diredp-fileset-1 flset-name)) + +;;;###autoload +(defun diredp-fileset-other-window (flset-name) ; Bound to `C-x 4 D S' + "Open Dired in another window on the files in fileset FLSET-NAME." + (interactive + (progn (unless (require 'filesets nil t) (error "Feature `filesets' not provided")) + (unless filesets-data (error "`filesets-data' is empty")) + (list (completing-read "Open Dired on fileset, in other window: " filesets-data)))) + (diredp-fileset-1 flset-name 'OTHER-WINDOW)) + +(defun diredp-fileset-1 (flset-name &optional other-window-p) + "Helper for `diredp-fileset(-other-window)'." + (let ((flset (filesets-get-fileset-from-name flset-name)) + (files ()) + (mode nil) + (dirfun (if other-window-p #'dired-other-window #'dired))) + (unless (or (setq mode (filesets-entry-mode flset)) ; ("my-fs" (:files "a" "b")) + (setq flset (cons "dummy" flset) ; (:files "a" "b") + mode (filesets-entry-mode flset))) + (error "Bad fileset: %S" flset-name)) + (message "Gathering file names...") + (dolist (file (filesets-get-filelist flset mode)) (push file files)) + (funcall dirfun (cons (generate-new-buffer-name flset-name) + (nreverse (mapcar (lambda (file) + (if (file-name-absolute-p file) + (expand-file-name file) + file)) + files)))))) + +;;;###autoload +(defun diredp-dired-this-subdir (&optional tear-off-p msgp) + "Open Dired for the subdir at or above point. +If point is not on a subdir line, but is in an inserted subdir +listing, then use that subdir. + +With a prefix arg: + If the subdir is inserted and point is in the inserted listing then + remove that listing and move to the ordinary subdir line. In other + words, when in an inserted listing, a prefix arg tears off the + inserted subdir to its own Dired buffer." + (interactive "P\np") + (diredp-ensure-mode) + (let* ((this-dir default-directory) + (this-subdir (diredp-this-subdir)) + (on-dir-line-p (atom this-subdir))) + (unless on-dir-line-p ; Subdir header line or non-directory file. + (setq this-subdir (car this-subdir))) + (unless (string= this-subdir this-dir) + (when tear-off-p + (unless on-dir-line-p + (dired-kill-subdir) ; Tear it off. + (dired-goto-file this-subdir))) ; Move to normal subdir line. + (dired-other-window this-subdir)))) + +;;;###autoload +(defun diredp-dired-inserted-subdirs (&optional no-show-p msgp) ; Bound to `C-M-i' + "Open Dired for each of the subdirs inserted in this Dired buffer. +A separate Dired buffer is used for each of them. +With a prefix arg, create the Dired buffers but do not display them. +Markings and current Dired switches are preserved." + (interactive "P\np") + (diredp-ensure-mode) + (let ((this-dir default-directory) + (this-buff (current-buffer)) + (this-frame (selected-frame)) + marked) + (unwind-protect + (save-selected-window + (dolist (entry dired-subdir-alist) + (unless (string= (car entry) this-dir) + (setq marked (with-current-buffer this-buff + (dired-remember-marks (dired-get-subdir-min entry) (dired-get-subdir-max entry)))) + (if (not no-show-p) + (dired-other-window (car entry) dired-actual-switches) + (dired-noselect (car entry) dired-actual-switches) + (when msgp (message "Dired buffers created but not shown"))) + (set-buffer this-buff) + (let ((inhibit-read-only t)) + (dired-mark-remembered marked)) + (set-buffer-modified-p nil)))) + (select-frame-set-input-focus this-frame)))) + + +;;; Actions on marked files and subdirs, recursively. + +(defun diredp-get-subdirs (&optional ignore-marks-p predicate details) + "Return subdirs from this Dired buffer and from marked subdirs, recursively. +If optional arg IGNORE-MARKS-P is non-nil then include all +subdirectories. Otherwise, include only those that are marked. + +Non-nil optional arg PREDICATE means include only subdirectory names +for which the PREDICATE returns non-nil. PREDICATE must accept a file +name as its only required argument. + +Optional arg DETAILS is passed to `diredp-get-files'." + (diredp-get-files ignore-marks-p (if predicate + `(lambda (name) (and (file-directory-p name) (funcall ,predicate name))) + #'file-directory-p) + 'INCLUDE-DIRS-P 'DONT-ASKP 'ONLY-MARKED-P details)) + +(defun diredp-get-files (&optional ignore-marks-p predicate include-dirs-p dont-askp only-marked-p details) + "Return file names from this Dired buffer and subdirectories, recursively. +The names are those that are marked in the current Dired buffer, or +all files in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +If there is some included subdirectory that has a Dired buffer with +marked files, then (unless DONT-ASKP is non-nil) this asks you whether +to use the marked files in Dired buffers, as opposed to using all of +the files in included directories. To this y-or-n question you can +hit `l' to see the list of files that will be included (using +`diredp-list-files'). In that `l' listing you can mouseover to see +image-file previews or use `RET' or `mouse-2' to visit files. + +\(Directories in `icicle-ignored-directories' are skipped, if you use +Icicles. Otherwise, directories in `vc-directory-exclusion-list' are +skipped.) + +Non-nil IGNORE-MARKS-P means ignore all Dired markings: just get all +of the files in the current directory (and all of the subdirectories, +if INCLUDE-DIRS-P is non-nil). + +Non-nil PREDICATE means include only file names for which the +PREDICATE returns non-nil. PREDICATE must accept a file name as its +only required argument. + +Non-nil INCLUDE-DIRS-P means include marked subdirectory names (but +also handle those subdirs recursively, picking up their marked files +and subdirs). + +Non-nil DONT-ASKP means do not ask the user whether to use marked +instead of all. Act as if the user was asked and replied `y'. + +Non-nil optional arg ONLY-MARKED-P means collect only marked files, +instead of collecting all files if none are marked. This argument is +ignored if IGNORE-MARKS-P is non-nil. + +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (let ((askp (list nil))) ; The cons's car will be set to `t' if need to ask user. + (if ignore-marks-p + (diredp-files-within (directory-files default-directory 'FULL diredp-re-no-dot) + () nil include-dirs-p predicate) + ;; Pass FILES and ASKP to `diredp-get-files-for-dir', so we don't have to use them as + ;; free vars there. But that means that they each need to be a cons cell that we can + ;; modify, so we can get back the updated info. + (let ((files (list 'DUMMY))) ; The files picked up will be added to this list. + (diredp-get-files-for-dir default-directory files askp include-dirs-p only-marked-p) + (setq files (cdr files)) ; Remove `DUMMY' from the modifed list. + (if (or dont-askp + (not (car askp)) + (diredp-y-or-n-files-p "Use marked (instead of all) in subdir Dired buffers? " + files predicate details)) + (if predicate (diredp-remove-if-not predicate files) files) + (setq files ()) + (dolist (file (diredp-marked-here)) + (if (not (file-directory-p file)) + (when (or (not predicate) (funcall predicate file)) + (add-to-list 'files file)) + (when include-dirs-p (setq files (nconc files (list file)))) + (setq files (nconc files (diredp-files-within (directory-files file 'FULL diredp-re-no-dot) + () nil include-dirs-p predicate))))) + (nreverse files)))))) + +(defun diredp-get-files-for-dir (directory accum askp &optional include-dirs-p only-marked-p) + "Return marked file names for DIRECTORY and subdirectories, recursively. +Pick up names of all marked files in DIRECTORY if it has a Dired +buffer, or all files in DIRECTORY if not. Handle subdirs recursively +\(only marked subdirs, if Dired). + +ACCUM is an accumulator list: the files picked up in this call are +nconc'd to it. + +ASKP is a one-element list, the element indicating whether to ask the +user about respecting Dired markings. It is set here to `t' if there +is a Dired buffer for DIRECTORY. + +Non-nil optional arg INCLUDE-DIRS-P means include marked subdirectory +names (but also handle those subdirs recursively). + +Non-nil optional arg ONLY-MARKED-P means collect only marked files, +instead of collecting all files if none are marked. + +If there is more than one Dired buffer for DIRECTORY then raise an +error." + (let ((dbufs (dired-buffers-for-dir (expand-file-name directory)))) + (dolist (file (if (not dbufs) + (and (not only-marked-p) (directory-files directory 'FULL diredp-re-no-dot)) + (when (cadr dbufs) (error "More than one Dired buffer for `%s'" directory)) + (unless (equal directory default-directory) (setcar askp t)) + (with-current-buffer (car dbufs) (diredp-marked-here only-marked-p 'NO-DOT-DOT)))) + (if (not (file-directory-p file)) + (setcdr (last accum) (list file)) + (when include-dirs-p (setcdr (last accum) (list file))) + (diredp-get-files-for-dir file accum askp include-dirs-p only-marked-p))))) + +(defun diredp-marked-here (&optional only-marked-p no-dot-dot-p) + "Marked files and subdirs in this Dired buffer, or all if none are marked. +Non-nil optional arg ONLY-MARKED-P means return nil if none are +marked. +Non-nil optional arg NO-DOT-DOT-P means do not include marked `..'." + ;; If no file is marked, exclude `(FILENAME)': the unmarked file at cursor. + ;; If there are no marked files as a result, return all files and subdirs in the dir. + (let* ((dired-marker-char ?*) + (ff (condition-case nil ; Ignore error if on `.' or `..' and no file is marked. + (dired-get-marked-files + nil nil (and no-dot-dot-p + (lambda (mf) (not (diredp-string-match-p "/\\.\\.$" mf)))) + 'DISTINGUISH-ONE-MARKED) + (error nil)))) + (cond ((eq t (car ff)) (cdr ff)) ; Single marked + ((cadr ff) ff) ; Multiple marked + (t (and (not only-marked-p) ; None marked + (directory-files default-directory 'FULL diredp-re-no-dot 'NOSORT)))))) + +(defun diredp-y-or-n-files-p (prompt files &optional predicate details) + "PROMPT user with a \"y or n\" question about a list of FILES. +Return t if answer is \"y\". Otherwise, return nil. + +Like `y-or-n-p', but you can also hit `l' to display the list of files +that the confirmation is for, in buffer `*Files'. In that `'l' +listing you can mouseover to see image-file previews or use `RET' or +`mouse-2' to visit files. + +When finished, buffer `*Files*' is killed if it was never shown, or is +hidden and buried otherwise. Thus, if it was shown then it is still +available to revisit afterward (even if you quit using `C-g'). + +PREDICATE is passed to `diredp-list-files', to list only file names +for which it returns non-nil. + +DETAILS is passed to `diredp-list-files', to show details about FILES." + (let ((answer 'recenter)) + (cond (noninteractive + (setq prompt (concat prompt + (and (not (eq ?\ (aref prompt (1- (length prompt))))) " ") + "(y or n; l to show file list) ")) + (let ((temp-prompt prompt)) + (while (not (memq answer '(act skip))) + (let ((str (read-string temp-prompt))) + (cond ((member str '("y" "Y")) (setq answer 'act)) + ((member str '("n" "N")) (setq answer 'skip)) + (t (setq temp-prompt (concat "Please answer y or n. " prompt)))))))) + ((if (not (fboundp 'display-popup-menus-p)) + (and window-system (listp last-nonmenu-event) use-dialog-box) + (and (display-popup-menus-p) (listp last-nonmenu-event) use-dialog-box)) + (setq answer (x-popup-dialog t `(,prompt ("Yes" . act) ("No" . skip))))) + (t + (let ((list-buf (generate-new-buffer-name "*Files*")) + (list-was-shown nil)) + (unwind-protect + (progn + (define-key query-replace-map "l" 'show) + (setq prompt (concat prompt + (and (eq ?\ (aref prompt (1- (length prompt)))) + "" " ") + "(y or n; l to show file list) ")) + (while (let* ((reprompt-actions '(recenter scroll-up scroll-down + scroll-other-window scroll-other-window-down)) + (key (let ((cursor-in-echo-area t)) + (when minibuffer-auto-raise + (raise-frame (window-frame (minibuffer-window)))) + (if (fboundp 'read-key) + (read-key (propertize + (if (memq answer reprompt-actions) + prompt + (concat "Please answer y or n. " prompt)) + 'face 'minibuffer-prompt)) + (read-char-exclusive + (if (memq answer reprompt-actions) + prompt + (concat "Please answer y or n. " prompt))))))) + (setq answer (lookup-key query-replace-map (vector key) t)) + (case answer + ((skip act) nil) + (recenter (recenter) t) + (show (diredp-list-files files nil list-buf predicate details) + (setq list-was-shown t)) ; Record showing it. + (help (message "Use `l' to show file list") (sit-for 1)) + (scroll-up (condition-case nil (scroll-up-command) (error nil)) t) + (scroll-down (condition-case nil (scroll-down-command) (error nil)) t) + (scroll-other-window (condition-case nil (scroll-other-window) (error nil)) t) + (scroll-other-window-down (condition-case nil (scroll-other-window-down nil) + (error nil)) t) + ((exit-prefix quit) (signal 'quit nil) t) + (t (or (not (eq key ?\e)) (progn (signal 'quit nil) t))))) + (ding) + (discard-input))) + (when (get-buffer list-buf) + (save-window-excursion (pop-to-buffer list-buf) + (condition-case nil ; Ignore error if user already deleted. + (if (one-window-p) (delete-frame) (delete-window)) + (error nil)) + (if list-was-shown (bury-buffer list-buf) (kill-buffer list-buf)))) + (define-key query-replace-map "l" nil))))) + (let ((ret (eq answer 'act))) + (unless noninteractive (message "%s %s" prompt (if ret "y" "n"))) + ret))) + +(defvar diredp-list-files-map + (let ((map (make-sparse-keymap))) + (define-key map "q" 'quit-window) + (define-key map "\r" 'diredp-find-line-file-other-window) + (define-key map [mouse-2] 'diredp-mouse-find-line-file-other-window) + map) + "Keymap for `diredp-list-files' output.") +(fset 'diredp-list-files-map diredp-list-files-map) + +;;;###autoload +(defun diredp-find-line-file-other-window () + "Visit file named by current line, in another window. +The full text of the line is used as the file name." + (interactive) + (let ((file (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + (when file (find-file-other-window file)))) + +;;;###autoload +(defun diredp-mouse-find-line-file-other-window (e) + "Visit file named by clicked line, in another window. +The full text of the line is used as the file name." + (interactive "e") + (save-excursion (mouse-set-point e) (diredp-find-line-file-other-window))) + +;;;###autoload +(defun diredp-list-marked (&optional arg predicate interactivep details) ; Bound to `C-M-l' + "List the marked files in this Dired buffer. +A prefix arg specifies files to use instead of the marked files: + + * Numeric prefix arg N: The next N files (previous -N, if < 0). + * C-u C-u: All files, but no directories. + * C-u C-u C-u: All files and directories, except `.' and `..' + * C-u C-u C-u C-u: All files and directories, including `.' and `..' + * Any other prefix arg: The current line's file only. + +You can use `RET' or `mouse-2' to visit any of the files. +If `tooltip-mode' is on then moving the mouse over image-file names +shows image previews. + +When called from Lisp: + Non-nil optional arg PREDICATE is a file-name predicate. List only + the files for which it returns non-nil. + Non-nil optional arg DETAILS is passed to `diredp-list-files'." + (interactive (progn (diredp-ensure-mode) (list current-prefix-arg nil t diredp-list-file-attributes))) + (let ((files (dired-get-marked-files nil arg predicate 'DISTINGUISH-ONE interactivep))) + (diredp-list-files files nil nil nil details))) + +(defun diredp-list-files (files &optional dir bufname predicate details) + "Display FILES, a list of file names. Wildcard patterns are expanded. +The files are shown in a new buffer, `*Files*' by default. + +Optional arg DIR serves as the default directory for expanding file + names that are not absolute. It defaults to `default-directory'. + +Optional arg BUFNAME is the name of the buffer for the display. + It defaults to `*Files*' (or `*Files*<N>' if `*Files*' exists). + +Optional arg PREDICATE is a predicate used to filter FILES: only files + satisfying PREDICATE are listed. + +Non-nil arg DETAILS means show details about each file, in addition to +the file name. It is passed to `diredp-list-file' (which see). + +File names listed are absolute. Mouseover gives help or an image-file +preview, and you can use `RET' or `mouse-2' to visit files." + (unless bufname (setq bufname (generate-new-buffer-name "*Files*"))) + (diredp-with-help-window + bufname + (princ "Files\n-----\n\n") + (let ((all-files-no-wildcards ()) + file-alist file-dir) + (dolist (file files) + (unless (or (string= file "") ; Ignore empty file names. + (and predicate (not (funcall predicate file)))) + (if (not (diredp-string-match-p "[[?*]" file)) + (add-to-list 'all-files-no-wildcards (diredp-list-file file details)) + (setq file-dir (or (file-name-directory file) default-directory) + file-alist (directory-files-and-attributes file-dir 'FULL "[[?*]" 'NOSORT)) + (dolist (ff file-alist) + (add-to-list 'all-files-no-wildcards (diredp-list-file file details)))))) + (save-excursion (dolist (fff (nreverse all-files-no-wildcards)) + (princ fff) (terpri))))) + (with-current-buffer bufname + (let ((buffer-read-only nil)) + (save-excursion + (goto-char (point-min)) + (forward-line 3) + (while (not (eobp)) + (add-text-properties (line-beginning-position) (line-end-position) + '(mouse-face highlight help-echo diredp-mouseover-help dired-filename t + ;; `keymap' does not work for Emacs 20. Could use `local-map' + ;; but that still leaves `RET' bound to `help-follow'. + keymap diredp-list-files-map)) + (forward-line 1)))) + (set-buffer-modified-p nil) + (setq buffer-read-only t) + (buffer-enable-undo))) + +(defun diredp-list-file (file &optional details) + "Return FILE name, expanded. +Non-nil optional arg DETAILS means append details about FILE to the +returned string. + +If DETAILS is a list of file attribute numbers then include only the +values of those attributes. Otherwise, include all attribute values." + (let ((file-dir (and details (or (file-name-directory file) default-directory))) + attrs) + (setq file (expand-file-name file file-dir)) + (when (and details (atom details)) (setq details '(0 1 2 3 4 5 6 7 8 9 10 11))) + (concat + file + (and details + (setq attrs (file-attributes file)) + (concat + "\n" + (and (memq 0 details) + (format " File Type: %s\n" + (cond ((eq t (nth 0 attrs)) "Directory") + ((stringp (nth 0 attrs)) (format "Symbolic link to `%s'" (nth 0 attrs))) + (t "Normal file")))) + (and (memq 8 details) + (format " Permissions: %s\n" (nth 8 attrs))) + (and (memq 7 details) (not (eq t (nth 0 attrs))) + (format " Size in bytes: %g\n" (nth 7 attrs))) + (and (memq 4 details) + (format-time-string " Time of last access: %a %b %e %T %Y (%Z)\n" (nth 4 attrs))) + (and (memq 5 details) + (format-time-string " Time of last modification: %a %b %e %T %Y (%Z)\n" (nth 5 attrs))) + (and (memq 6 details) + (format-time-string " Time of last status change: %a %b %e %T %Y (%Z)\n" (nth 6 attrs))) + (and (memq 1 details) + (format " Number of links: %d\n" (nth 1 attrs))) + (and (memq 2 details) + (format " User ID (UID): %s\n" (nth 2 attrs))) + (and (memq 3 details) + (format " Group ID (GID): %s\n" (nth 3 attrs))) + (and (memq 10 details) + (format " Inode: %S\n" (nth 10 attrs))) + (and (memq 11 details) + (format " Device number: %s\n" (nth 11 attrs)))))))) + +(defvar diredp-files-within-dirs-done () + "Directories already processed by `diredp-files-within'.") + + +;; Not used in the `Dired+' code yet. +(defun diredp-directories-within (&optional directory no-symlinks-p predicate) + "List of accessible directories within DIRECTORY. +Directories in `icicle-ignored-directories' are skipped, if you use +Icicles. Otherwise, directories in `vc-directory-exclusion-list' are +skipped. + +Optional arg DIRECTORY defaults to the value of `default-directory'. +Non-nil optional arg NO-SYMLINKS-P means do not follow symbolic links. +Non-nil optional arg PREDICATE must be a function that accepts a + file-name argument. Only directories that satisfy PREDICATE are + included in the result." + (unless directory (setq directory default-directory)) + (let ((dirs (diredp-files-within (directory-files directory 'FULL diredp-re-no-dot) + () no-symlinks-p 'INCLUDE-DIRS-P + #'file-directory-p))) + (if predicate (diredp-remove-if-not predicate dirs) dirs))) + +;; Args INCLUDE-DIRS-P and PREDICATE are not used in the `Dired+' code yet +;; (except in `diredp-directories-within', which also is not used yet). +;; +(defun diredp-files-within (file-list accum &optional no-symlinks-p include-dirs-p predicate) + "List of readable files in FILE-LIST, handling directories recursively. +FILE-LIST is a list of file names or a function that returns such. +If a function then invoke it with no args to get the list of files. + +Accessible directories in the list of files are processed recursively +to include their files and the files in their subdirectories. The +directories themselves are not included, unless optional arg +INCLUDE-DIRS-P is non-nil. (Directories in +`icicle-ignored-directories' are skipped, if you use Icicles. +Otherwise, directories in `vc-directory-exclusion-list' are skipped.) + +But if there is a Dired buffer for such a directory, and if FILE-LIST +is a function, then it is invoked in that Dired buffer to return the +list of files to use. E.g., if FILE-LIST is `dired-get-marked-files' +then only the marked files and subdirectories are included. If you +have more than one Dired buffer for a directory that is processed +here, then only the first one in `dired-buffers' is used. + +The list of files is accumulated in ACCUM, which is used for recursive +calls. + +Non-nil optional arg NO-SYMLINKS-P means do not follow symbolic links. + +Non-nil optional arg INCLUDE-DIRS-P means include directory names +along with the names of non-directories. + +Non-nil optional arg PREDICATE must be a function that accepts a +file-name argument. Only files (and possibly directories) that +satisfy PREDICATE are included in the result." + ;; Bind `diredp-files-within-dirs-done' for use as a free var in `diredp-files-within-1'. + (let ((diredp-files-within-dirs-done ())) + (nreverse (diredp-files-within-1 file-list accum no-symlinks-p include-dirs-p predicate)))) + +;; `diredp-files-within-dirs-done' is free here, bound in `diredp-files-within'. +(defun diredp-files-within-1 (file-list accum no-symlinks-p include-dirs-p predicate) + "Helper for `diredp-files-within'." + (let ((files (if (functionp file-list) (funcall file-list) file-list)) + (res accum) + file) + (when (and files predicate) (setq files (diredp-remove-if-not predicate files))) + (while files + (setq file (car files)) + (unless (and no-symlinks-p (file-symlink-p file)) + (if (file-directory-p file) + ;; Skip directory if ignored, already treated, or inaccessible. + (when (and (not (member (file-name-nondirectory file) + (if (boundp 'icicle-ignored-directories) + icicle-ignored-directories + (and (boundp 'vc-directory-exclusion-list) + vc-directory-exclusion-list)))) + (not (member (file-truename file) diredp-files-within-dirs-done)) + (file-accessible-directory-p file)) + (setq res (diredp-files-within-1 (or (and (functionp file-list) + (dired-buffers-for-dir + (expand-file-name file)) ; Removes killed buffers. + (with-current-buffer + (cdr (assoc (file-name-as-directory file) + dired-buffers)) + (funcall file-list))) + (directory-files file 'FULL diredp-re-no-dot)) + res no-symlinks-p include-dirs-p predicate)) + (when include-dirs-p (push file res)) + (push (file-truename file) diredp-files-within-dirs-done)) + (when (file-readable-p file) (push file res)))) + (pop files)) + res)) + +(defun diredp-remove-if (pred xs) + "A copy of list XS with no elements that satisfy predicate PRED." + (let ((result ())) + (dolist (x xs) (unless (funcall pred x) (push x result))) + (nreverse result))) + +(defun diredp-remove-if-not (pred xs) + "A copy of list XS with only elements that satisfy predicate PRED." + (let ((result ())) + (dolist (x xs) (when (funcall pred x) (push x result))) + (nreverse result))) + +(when (> emacs-major-version 21) ; Emacs 20 has no PREDICATE arg to `read-file-name'. + (defun diredp-insert-as-subdir (child ancestor &optional in-dired-now-p) + "Insert the current Dired dir into a Dired listing of an ancestor dir. +Ancestor means parent, grandparent, etc. at any level. +You are prompted for the ancestor directory. +The ancestor Dired buffer is selected. + +Markings and switches in the current Dired buffer are preserved for +the subdir listing in the ancestor Dired buffer. + +Note: If you use Icicles, then you can use +`icicle-dired-insert-as-subdir' instead: it is a multi-command. It +does the same thing, but it lets you insert any number of descendant +directories into a given ancestor-directory Dired buffer. + +Non-interactively: + Insert CHILD dir into Dired listing for ANCESTOR dir. + + Non-nil optional arg IN-DIRED-NOW-P means to use the current buffer + as the Dired buffer from which to pick up markings and switches. + Otherwise, pick them up from a Dired buffer for CHILD, if there is + exactly one such buffer." + (interactive (progn (diredp-ensure-mode) + (list default-directory + (completing-read + "Insert this dir into ancestor dir: " + (mapcar #'list (diredp-ancestor-dirs default-directory))) + t))) + (let ((child-dired-buf (if in-dired-now-p + (current-buffer) + (dired-buffers-for-dir (expand-file-name child)))) + (switches ()) + (marked ())) + (when (consp child-dired-buf) + (setq child-dired-buf (and (= 1 (length child-dired-buf)) (car child-dired-buf)))) + (when child-dired-buf + (with-current-buffer child-dired-buf + (setq switches dired-actual-switches + marked (dired-remember-marks (point-min) (point-max))))) + (dired-other-window ancestor) + (dired-insert-subdir child switches) + (when marked (let ((inhibit-read-only t)) (dired-mark-remembered marked))) + (set-buffer-modified-p nil)))) + +(defun diredp-ancestor-dirs (dir) + "Return a list of the ancestor directories of directory DIR." + (mapcar #'file-name-as-directory + (diredp-maplist (lambda (dd) (mapconcat #'identity (reverse dd) "/")) + (cdr (nreverse (split-string dir "/" t)))))) + +(defun diredp-maplist (function list) + "Map FUNCTION over LIST and its cdrs. +A simple, recursive version of the classic `maplist'." + (and list (cons (funcall function list) (diredp-maplist function (cdr list))))) + +(defun diredp-existing-dired-buffer-p (buffer-name) + "Return non-nil if BUFFER-NAME names a live, existing Dired buffer." + (let ((dbuf (get-buffer buffer-name))) + (and dbuf (buffer-live-p dbuf) (rassq dbuf dired-buffers)))) + +;; From `cl-seq.el', function `union', without keyword treatment. +;; (Same as `icicle-set-union' in `icicles-fn.el'.) +(defun diredp-set-union (list1 list2) + "Combine LIST1 and LIST2 using a set-union operation. +The result list contains all items that appear in either LIST1 or +LIST2. Comparison is done using `equal'. This is a non-destructive +function; it copies the data if necessary." + (cond ((null list1) list2) + ((null list2) list1) + ((equal list1 list2) list1) + (t + (unless (>= (length list1) (length list2)) + (setq list1 (prog1 list2 (setq list2 list1)))) ; Swap them. + (while list2 + (unless (member (car list2) list1) (setq list1 (cons (car list2) list1))) + (setq list2 (cdr list2))) + list1))) + +(when (fboundp 'file-equal-p) ; Emacs 24+ + (defun diredp-move-file (file &optional prompt-anyway) + "Move FILE to associated directory in `diredp-move-file-dirs'. +If no association, or if you use a prefix arg, prompt for directory." + (interactive (list (dired-get-filename) current-prefix-arg)) + (unless file (error "No file specified")) + (let* ((file-sans (file-name-nondirectory file)) + (dir (file-name-as-directory + (or (and (not prompt-anyway) + (cdr (assoc file-sans diredp-move-file-dirs))) + (read-directory-name "Move to: "))))) + (when (file-equal-p dir (file-name-directory file)) + (error "Cannot move to same directory: %s" dir)) + (dired-rename-file file dir nil) + (dired-add-file (expand-file-name file-sans dir)) + (message "Moved `%s' to `%s'" file-sans dir)))) + +(defvar diredp-last-copied-filenames () + "String list of file names last copied to the `kill-ring'. +Copying is done by `dired-copy-filename-as-kill' and related commands.") + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Put text copied to kill ring in variable `diredp-last-copied-filenames'. +;; +(defun dired-copy-filename-as-kill (&optional arg) + "Copy names of marked (or next ARG) files into the kill ring. +The names are separated by a space. +With a zero prefix arg, use the absolute file name of each marked file. +With \\[universal-argument], use the file name relative to the Dired buffer's +`default-directory'. (This still may contain slashes if in a subdirectory.) + +If on a subdir headerline, use absolute subdirname instead; +prefix arg and marked files are ignored in this case. + +You can then feed the file name(s) to other commands with \\[yank]. + +The value of global variable `diredp-last-copied-filenames' is updated +to the string list of file name(s), so you can obtain it even after +the kill ring is modified." + (interactive "P") + (let* ((num-arg (prefix-numeric-value arg)) + (string (or (dired-get-subdir) + (mapconcat #'identity + (cond ((not arg) (dired-get-marked-files 'no-dir)) + ((zerop num-arg) (dired-get-marked-files)) + ((consp arg) (dired-get-marked-files t)) + (t (dired-get-marked-files 'no-dir num-arg))) + " ")))) + (unless (string= "" string) + (if (eq last-command 'kill-region) (kill-append string nil) (kill-new string)) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer)) + (message "%s" string)))) + +(defun diredp-copy-abs-filenames-as-kill () ; Not bound. + "Copy absolute names of marked files in Dired to the kill ring. +Also set variable `diredp-last-copied-filenames' to the string that +lists the file names. + +This is the same as using a zero prefix arg with command +`dired-copy-filename-as-kill', that is, \\<dired-mode-map>`M-0 \\[dired-copy-filename-as-kill]'." + (interactive (diredp-ensure-mode)) + (dired-copy-filename-as-kill 0)) + +;;;###autoload +(defalias 'diredp-paste-files 'diredp-yank-files) ; Bound to `C-y'. +;;;###autoload +(defun diredp-yank-files (&optional dir no-confirm-p details) + "Paste files, whose absolute names you copied, to the current directory. +With a non-negative prefix arg you are instead prompted for the target + directory. +With a non-positive prefix arg you can see details about the files if + you hit `l' when prompted to confirm pasting. Otherwise you see only + the file names. The details you see are defined by option + `diredp-list-file-attributes'. + +You should have copied the list of file names as a string to the kill +ring using \\<dired-mode-map>`M-0 \\[dired-copy-filename-as-kill]' or \ +\\[diredp-copy-abs-filenames-as-kill]. +Those commands also set variable `diredp-last-copied-filenames' to the +same string. `diredp-yank-files' uses the value of that variable, not +whatever is currently at the head of the kill ring. + +When called from Lisp: + +Optional arg NO-CONFIRM-P means do not ask for confirmation to copy. +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (interactive (list (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) + (expand-file-name (read-directory-name "Yank files to directory: "))) + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0) + diredp-list-file-attributes))) + (setq dir (or dir (and (derived-mode-p 'dired-mode) (dired-current-directory)))) + (unless (file-directory-p dir) (error "Not a directory: `%s'" dir)) + (let ((files diredp-last-copied-filenames)) + (unless (stringp files) (error "No copied file names")) + (setq files (diredp-delete-if-not (lambda (file) (file-name-absolute-p file)) (split-string files))) + (unless files (error "No copied *absolute* file names (Did you use `M-0 w'?)")) + (if (and (not no-confirm-p) + (diredp-y-or-n-files-p "Paste files whose names you copied? " files nil details)) + (dired-create-files #'dired-copy-file "Copy" files + (lambda (from) (expand-file-name (file-name-nondirectory from) dir))) + (message "OK, file-pasting canceled")))) + +;;;###autoload +(defun diredp-move-files-named-in-kill-ring (&optional dir no-confirm-p details) ; Bound to `C-w' + "Move files, whose absolute names you copied, to the current directory. +With a non-negative prefix arg you are instead prompted for the target + directory. +With a non-positive prefix arg you can see details about the files if + you hit `l' when prompted to confirm pasting. Otherwise you see only + the file names. The details you see are defined by option + `diredp-list-file-attributes'. + +You should have copied the list of file names as a string to the kill +ring using \\<dired-mode-map>`M-0 \\[dired-copy-filename-as-kill]' or \ +\\[diredp-copy-abs-filenames-as-kill]. +Those commands also set variable `diredp-last-copied-filenames' to the +same string. `diredp-move-files-named-in-kill-ring' uses the value of +that variable, not whatever is currently at the head of the kill ring. + +When called from Lisp: + +Optional arg NO-CONFIRM-P means do not ask for confirmation to move. +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (interactive (list (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) + (expand-file-name (read-directory-name "Move files to directory: "))) + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0) + diredp-list-file-attributes))) + (setq dir (or dir (and (derived-mode-p 'dired-mode) (dired-current-directory)))) + (unless (file-directory-p dir) (error "Not a directory: `%s'" dir)) + (let ((files diredp-last-copied-filenames)) + (unless (stringp files) (error "No copied file names")) + (setq files (diredp-delete-if-not (lambda (file) (file-name-absolute-p file)) (split-string files))) + (unless files (error "No copied (absolute* file names (Did you use `M-0 w'?)")) + (if (and (not no-confirm-p) + (diredp-y-or-n-files-p "MOVE files whose names you copied? " files nil details)) + (dired-create-files #'dired-rename-file "Move" files + (lambda (from) (expand-file-name (file-name-nondirectory from) dir))) + (message "OK, file-moves canceled")))) + + +;;; Commands operating on marked at all levels below (recursively) + +(defun diredp-get-confirmation-recursive (&optional type) + "Get confirmation from user to act on all TYPE here and below. +If TYPE is nil use \"files\" in the confirmation prompt, else use TYPE. +Raise an error if not confirmed. +Raise an error first if not in Dired mode." + (diredp-ensure-mode) + (unless (y-or-n-p (format "Act on ALL %s (or all marked if any) in and UNDER this dir? " + (or type 'files))) + (error "OK, canceled"))) + +;;;###autoload +(when (> emacs-major-version 21) ; Emacs 22+ has KILL-ROOT parameter. + (defun diredp-kill-this-tree () + "Remove this subdir listing and lower listings." + (interactive) + (dired-kill-tree (dired-current-directory) nil 'KILL-ROOT))) + +;;;###autoload +(defun diredp-insert-subdirs (&optional switches interactivep) ; Bound to `M-i' + "Insert the marked subdirectories. +Like using \\<dired-mode-map>`\\[dired-maybe-insert-subdir]' at each marked directory line." + (interactive (list (and current-prefix-arg + (read-string "Switches for listing: " + (or (and (boundp 'dired-subdir-switches) dired-subdir-switches) + dired-actual-switches))) + t)) + (dolist (subdir (dired-get-marked-files nil + nil + (lambda (fl) (and (file-directory-p fl) ; Exclude `.' and `..' + (not (diredp-string-match-p "/[.][.]?\\'" fl)))) + nil + interactivep)) + (dired-maybe-insert-subdir subdir switches))) + +;;;###autoload +(defun diredp-insert-subdirs-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M-i' + "Insert the marked subdirs, including those in marked subdirs. +Like `diredp-insert-subdirs', but act recursively on subdirs. +The subdirs inserted are those that are marked in the current Dired +buffer, or ALL subdirs in the directory if none are marked. Marked +subdirectories are handled recursively in the same way (their marked +subdirs are inserted...). + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive 'subdirs) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (subdir (diredp-get-files ignore-marks-p #'file-directory-p 'INCLUDE-SUBDIRS-P nil nil details)) + (dired-maybe-insert-subdir subdir))) + +;;;###autoload +(defun diredp-do-shell-command-recursive (command &optional ignore-marks-p details) ; Bound to `M-+ !' + "Run shell COMMAND on the marked files, including those in marked subdirs. +Like `dired-do-shell-command', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((prompt "! on *: ") + (cmd (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + 'minibuffer-default-add-dired-shell-commands)) + (let ((dired-no-confirm t)) + (if (functionp 'dired-guess-shell-command) + ;; Guess cmd based only on files marked in current (top) dir. + (dired-guess-shell-command prompt (dired-get-marked-files t)) + (read-shell-command prompt nil nil)))))) + (list cmd current-prefix-arg diredp-list-file-attributes)))) + (dired-do-shell-command command nil (diredp-get-files ignore-marks-p nil nil nil nil details))) + +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + + (defun diredp-do-async-shell-command-recursive (command &optional ignore-marks-p details) + ; Bound to `M-+ &' + "Run async shell COMMAND on marked files, including in marked subdirs. +Like `dired-do-async-shell-command', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((prompt "! on *: ") + (cmd (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + 'minibuffer-default-add-dired-shell-commands)) + (let ((dired-no-confirm t)) + (if (functionp 'dired-guess-shell-command) + ;; Guess cmd based only on files marked in current (top) dir. + (dired-guess-shell-command prompt (dired-get-marked-files t)) + (read-shell-command prompt nil nil)))))) + (list cmd current-prefix-arg diredp-list-file-attributes)))) + (dired-do-async-shell-command command nil (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-do-symlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ S' + "Make symbolic links to marked files, including those in marked subdirs. +Like `dired-do-symlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'make-symbolic-link "Symlink" ignore-marks-p details)) + +(defun diredp-do-relsymlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ Y' + "Relative symlink all marked files, including those in marked subdirs into a dir. +Like `dired-do-relsymlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +For absolute symlinks, use \\[diredp-do-symlink-recursive]. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-make-relative-symlink "RelSymLink" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-hardlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ H' + "Add hard links for marked files, including those in marked subdirs. +Like `dired-do-hardlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-hardlink "Hardlink" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-print-recursive (&optional ignore-marks-p details) ; Bound to `M-+ P' + "Print the marked files, including those in marked subdirs. +Like `dired-do-print', but act recursively on subdirs to pick up the +files to print. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((file-list (diredp-get-files ignore-marks-p nil nil nil nil details)) + (command (dired-mark-read-string + "Print %s with: " + (mapconcat #'identity + (cons lpr-command (if (stringp lpr-switches) (list lpr-switches) lpr-switches)) + " ") + 'print nil file-list))) + (dired-run-shell-command (dired-shell-stuff-it command file-list nil)))) + +;;;###autoload +(defun diredp-image-dired-display-thumbs-recursive (&optional ignore-marks-p append do-not-pop details) + ; Bound to `M-+ C-t d' + "Display thumbnails of marked files, including those in marked subdirs. +Like `image-dired-display-thumbs', but act recursively on subdirs. +Optional arguments APPEND and DO-NOT-POP are as for +`image-dired-display-thumbs'. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg nil nil diredp-list-file-attributes))) + (let ((buf (image-dired-create-thumbnail-buffer)) + thumb-name files dired-buf) + (setq files (diredp-get-files ignore-marks-p nil nil nil nil details) + dired-buf (current-buffer)) + (with-current-buffer buf + (let ((inhibit-read-only t)) + (if append (goto-char (point-max)) (erase-buffer)) + (mapc (lambda (curr-file) + (setq thumb-name (image-dired-thumb-name curr-file)) + (if (and (not (file-exists-p thumb-name)) + (not (= 0 (image-dired-create-thumb curr-file thumb-name)))) + (message "Thumb could not be created for file %s" curr-file) + (image-dired-insert-thumbnail thumb-name curr-file dired-buf))) + files)) + (case image-dired-line-up-method + (dynamic (image-dired-line-up-dynamic)) + (fixed (image-dired-line-up)) + (interactive (image-dired-line-up-interactive)) + (none nil) + (t (image-dired-line-up-dynamic)))) + (if do-not-pop + (display-buffer image-dired-thumbnail-buffer) + (pop-to-buffer image-dired-thumbnail-buffer)))) + +;;;###autoload +(defun diredp-image-dired-tag-files-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C-t t' + "Tag marked files with an `image-dired' tag, including in marked subdirs. +Like `image-dired-tag-files', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((tag (read-string "Tags to add (separate tags with a semicolon): "))) + (image-dired-write-tags (mapcar (lambda (x) (cons x tag)) + (diredp-get-files ignore-marks-p nil nil nil nil details))))) + +;;;###autoload +(defun diredp-image-dired-delete-tag-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C-t r' + "Remove `image-dired' tag for marked files, including in marked subdirs. +Like `image-dired-delete-tag', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (image-dired-remove-tag (diredp-get-files ignore-marks-p nil nil nil nil details) + (read-string "Tag to remove: "))) + +;;;###autoload +(defun diredp-image-dired-comment-files-recursive (&optional ignore-marks-p details) + ; Bound to `M-+ C-t c' + "Add comment to marked files in dired, including those in marked subdirs. +Like `image-dired-dired-comment-files' but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((comment (image-dired-read-comment))) + (image-dired-write-comments (mapcar (lambda (curr-file) (cons curr-file comment)) + (diredp-get-files ignore-marks-p nil nil nil nil details))))) + +(when (> emacs-major-version 22) + + (defun diredp-do-decrypt-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : d' + "Decrypt marked files, including those in marked subdirs. +Like `epa-dired-do-decrypt', but act recursively on subdirs to pick up +the files to decrypt. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-decrypt-file (expand-file-name file))) + (revert-buffer)) + + (defun diredp-do-verify-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : v' + "Verify marked files, including those in marked subdirs. +Like `epa-dired-do-verify', but act recursively on subdirs to pick up +the files to verify. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-verify-file (expand-file-name file))) + (revert-buffer)) + + (defun diredp-do-sign-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : s' + "Sign marked files, including those in marked subdirs. +Like `epa-dired-do-sign', but act recursively on subdirs to pick up +the files to sign. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-sign-file (expand-file-name file) + (epa-select-keys (epg-make-context) "Select keys for signing. +If none are selected, the default secret key is used. ") + (y-or-n-p "Make a detached signature? "))) + (revert-buffer)) + + (defun diredp-do-encrypt-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : e' + "Encrypt marked files, including those in marked subdirs. +Like `epa-dired-do-encrypt', but act recursively on subdirs to pick up +the files to encrypt. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-encrypt-file (expand-file-name file) + (epa-select-keys (epg-make-context) "Select recipients for encryption. +If none are selected, symmetric encryption is performed. "))) + (revert-buffer))) + +;;;###autoload +(defun diredp-do-bookmark-recursive (&optional ignore-marks-p prefix details) ; Bound to `M-+ M-b' + "Bookmark the marked files, including those in marked subdirs. +Like `diredp-do-bookmark', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (diredp-bookmark prefix file 'NO-MSG-P))) + +;;;###autoload +(defun diredp-do-bookmark-dirs-recursive (ignore-marks-p &optional details msgp) + "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively. +Create a Dired bookmark for this directory and for each of its marked +subdirectories. Handle each of the marked subdirectory similarly: +bookmark it and its marked subdirectories, and so on, recursively. +Name each of these Dired bookmarks with the Dired buffer name. + +After creating the Dired bookmarks, create a sequence bookmark, named +`DIRBUF and subdirs', where DIRBUF is the name of the original buffer. +This bookmark represents the whole Dired tree rooted in the directory +where you invoked the command. Jumping to this sequence bookmark +restores all of the Dired buffers making up the tree, by jumping to +each of their bookmarks. + +With a prefix arg, bookmark the marked and unmarked subdirectory Dired +buffers, recursively, that is, ignore markings. + +Note: + +* If there is more than one Dired buffer for a given subdirectory then + only the first such is used. + +* This command creates new bookmarks. It never updates or overwrites + an existing bookmark. + +You need library `Bookmark+' for this command. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-subdirs'." + (interactive (progn (unless (featurep 'bookmark+) + (error "You need library `Bookmark+' for this command")) + (diredp-get-confirmation-recursive 'subdirs) + (list current-prefix-arg diredp-list-file-attributes t))) + (diredp-ensure-mode) + (let ((sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (snames ()) + dbufs) + (when (and msgp sdirs) (message "Checking descendant directories...")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((bname (bookmark-buffer-name)) + (count 2)) + (while (and (bmkp-get-bookmark-in-alist bname 'NOERROR) (setq bname (format "%s[%d]" bname count)))) + (bookmark-set bname nil nil 'NO-UPDATE-P) ; Inhibit updating displayed list. + (push bname snames))))) + (let ((bname (format "%s and subdirs" (bookmark-buffer-name))) + (count 2)) + (while (and (bmkp-get-bookmark-in-alist bname 'NOERROR) (setq bname (format "%s[%d]" bname count)))) + (bmkp-set-sequence-bookmark bname (nreverse snames) -1 'MSGP)) + (bmkp-refresh/rebuild-menu-list nil))) + +;;;###autoload +(defun diredp-do-bookmark-in-bookmark-file-recursive (bookmark-file ; Bound to `M-+ C-M-B', aka `M-+ C-M-S-b') + &optional prefix ignore-marks-p bfile-bookmarkp details) + "Bookmark files here and below in BOOKMARK-FILE and save BOOKMARK-FILE. +Like `diredp-do-bookmark-in-bookmark-file', but act recursively on +subdirs. The files included are those that are marked in the current +Dired buffer, or all files in the directory if none are marked. +Marked subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp: + * Optional arg BFILE-BOOKMARKP non-nil means create a bookmark-file + bookmark for BOOKMARK-FILE. + * Optional arg DETAILS is passed to `diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let ((d-r-b-f-args (diredp-read-bookmark-file-args))) + (list (car d-r-b-f-args) + (cadr d-r-b-f-args) + (car (cddr d-r-b-f-args)) + nil + diredp-list-file-attributes)))) + (diredp-do-bookmark-in-bookmark-file bookmark-file prefix nil bfile-bookmarkp + (diredp-get-files ignore-marks-p nil nil nil nil details))) + +;;;###autoload +(defun diredp-set-bookmark-file-bookmark-for-marked-recursive (bookmark-file + &optional prefix ignore-marks-p details) + ; Bound to `M-+ C-M-b' + "Bookmark the marked files and create a bookmark-file bookmark for them. +Like `diredp-set-bookmark-file-bookmark-for-marked', but act +recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-bookmark-in-bookmark-file-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (let ((d-r-b-f-args (diredp-read-bookmark-file-args))) + (list (car d-r-b-f-args) + (cadr d-r-b-f-args) + (car (cddr d-r-b-f-args)) + diredp-list-file-attributes)))) + (diredp-ensure-bookmark+) + (diredp-do-bookmark-in-bookmark-file-recursive + bookmark-file prefix ignore-marks-p 'CREATE-BOOKMARK-FILE-BOOKMARK details)) + +;;;###autoload +(defun diredp-do-find-marked-files-recursive (&optional arg details) ; Bound to `M-+ F' + "Find marked files simultaneously, including those in marked subdirs. +Like `dired-do-find-marked-files', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With (explicit) numeric prefix ARG >= 0, find the files but do not +display them. + +With numeric prefix ARG <= 0, ignore all marks - include all files in +this Dired buffer and all subdirs, recursively. + +Note that prefix-argument behavior is different for this command than +for `dired-do-find-marked-files'. In particular, a negative numeric +prefix arg does not cause the files to be shown in separate frames. +Only non-nil `pop-up-frames' (or equivalent configuration) causes +the files to be shown in separate frames. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((narg (prefix-numeric-value arg))) + (dired-simultaneous-find-file (diredp-get-files (<= narg 0) nil nil nil nil details) + (and arg (>= narg 0) narg)))) + +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + + (defun diredp-do-isearch-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M-s a C-s' + "Isearch the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (multi-isearch-files (diredp-get-files ignore-marks-p nil nil nil nil details))) + + (defun diredp-do-isearch-regexp-recursive (&optional ignore-marks-p details) ; `M-+ M-s a C-M-s' + "Regexp-Isearch the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (multi-isearch-files-regexp (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +(defun diredp-do-search-recursive (regexp &optional ignore-marks-p details) ; Bound to `M-+ A' + "Regexp-search the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Stops when a match is found. +To continue searching for the next match, use `\\[tags-loop-continue]'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list (read-string "Search marked files (regexp): ") + current-prefix-arg + diredp-list-file-attributes))) + (tags-search regexp '(diredp-get-files ignore-marks-p nil nil nil nil details))) + +;;;###autoload +(defun diredp-do-query-replace-regexp-recursive (from to &optional arg details) + ; Bound to `M-+ Q' + "Do `query-replace-regexp' on marked files, including in marked subdirs. +Query-replace FROM with TO. + +Like `dired-do-query-replace', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With an (explicit) numeric prefix argument: + +* >= 0 means ignore all marks - include ALL files in this Dired buffer + and all subdirs, recursively. + +* <= 0 means replace only word-delimited matches. + +If you exit (`\\[keyboard-quit]', `RET' or `q'), you can resume the query replacement +using `\\[tags-loop-continue]'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (let ((common (query-replace-read-args "Query replace regexp in marked files" t t))) + (list (nth 0 common) + (nth 1 common) + current-prefix-arg + diredp-list-file-attributes)))) + (let* ((narg (and arg (prefix-numeric-value arg))) + (delimited (and narg (<= narg 0))) + (ignore-marks-p (and narg (>= narg 0))) + (files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (fit-frame-min-width 30) + (fit-frame-min-height 15)) + (dolist (file files) + (let ((buffer (get-file-buffer file))) + (when (and buffer (with-current-buffer buffer buffer-read-only)) + (error "File `%s' is visited read-only" file)))) + (tags-query-replace from to delimited `',files))) + +;;;###autoload +(defun diredp-do-grep-recursive (command-args &optional details) ; Bound to `M+ C-M-G' + "Run `grep' on marked files, including those in marked subdirs. +Like `diredp-do-grep', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (unless (if (< emacs-major-version 22) + grep-command + (and grep-command (or (not grep-use-null-device) (eq grep-use-null-device t)))) + (grep-compute-defaults)) + (list (diredp-do-grep-1 + (diredp-get-files current-prefix-arg nil nil nil nil diredp-list-file-attributes))))) + (grep command-args)) + +;;;###autoload +(defun diredp-marked-recursive (dirname &optional ignore-marks-p details) ; Not bound to a key + "Open Dired on marked files, including those in marked subdirs. +Like `diredp-marked', but act recursively on subdirs. + +See `diredp-do-find-marked-files-recursive' for a description of the +files included. In particular, if no files are marked here or in a +marked subdir, then all files in the directory are included. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, DIRNAME here must be a string, not a cons. It +is used as the name of the new Dired buffer. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list nil current-prefix-arg diredp-list-file-attributes))) + (dired (cons (or dirname (generate-new-buffer-name (buffer-name))) + (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-marked-recursive-other-window (dirname &optional ignore-marks-p details) ; Bound to `M-+ C-M-*' + "Same as `diredp-marked-recursive', but uses a different window. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list nil current-prefix-arg diredp-list-file-attributes))) + (dired-other-window + (cons (or dirname (generate-new-buffer-name (buffer-name))) + (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-list-marked-recursive (&optional ignore-marks-p predicate details) ; Bound to `M-+ C-M-l' + "List the files marked here and in marked subdirs, recursively. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, all marks are ignored: all files in this Dired +buffer and all descendant directories are included. + +You can use `RET' or `mouse-2' to visit any of the files. +If `tooltip-mode' is on then moving the mouse over image-file names +shows image previews. + +When called from Lisp: + Non-nil optional arg IGNORE-MARKS-P means ignore marks. + Non-nil optional arg PREDICATE is a file-name predicate. List only + the files for which it returns non-nil. + Non-nil optional arg DETAILS is passed to `diredp-list-files'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg nil diredp-list-file-attributes))) + (let ((files (diredp-get-files ignore-marks-p predicate))) (diredp-list-files files nil nil nil details))) + +;;;###autoload +(defun diredp-flag-auto-save-files-recursive (&optional arg details) ; `M-+ #' + "Flag all auto-save files for deletion, including in marked subdirs. +A non-negative prefix arg means to unmark (unflag) them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, flag all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (let ((dired-marker-char dired-del-marker)) + (diredp-mark-recursive-1 arg "auto-save files" "auto-save file" '(diredp-looking-at-p "^.* #.+#$") details))) + +(when (fboundp 'char-displayable-p) ; Emacs 22+ + + (defun diredp-change-marks-recursive (old new &optional arg predicate details) ; `M-+ * c' + "Change all OLD marks to NEW marks, including those in marked subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +* A non-positive prefix arg means ignore subdir markings and act + instead on ALL subdirs. + +* A non-negative prefix arg means do not change marks on subdirs + themselves. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp: + Non-nil arg PREDICATE is a file-name predicate. Act on only the + files for which it returns non-nil. + DETAILS is passed to `diredp-get-subdirs'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((cursor-in-echo-area t) + (old (progn (message "Change (old mark): ") (read-char))) + (new (progn (message "Change `%c' marks to (new mark): " old) (read-char)))) + (list old new current-prefix-arg nil diredp-list-file-attributes)))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (nosubs (natnump numarg)) + (ignore-marks (and numarg (<= numarg 0))) + (dired-marker-char new) + (sdirs (diredp-get-subdirs ignore-marks predicate details)) + (old-strg (format "\n%c" old)) + (count 0) + dbufs) + (unless (char-displayable-p old) (error "Not a displayable character: `%c'" old)) + (unless (char-displayable-p new) (error "Not a displayable character: `%c'" new)) + (message "Changing mark `%c' to `%c'..." old new) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((inhibit-read-only t) + (file nil)) + (save-excursion + (goto-char (point-min)) + (while (search-forward old-strg nil t) + (save-match-data (setq file (dired-get-filename 'no-dir t))) + ;; Do nothing if changing from UNmarked and not on a file or dir name. + (unless (and (= old ? ) (not file)) + ;; Do nothing if marked subdir and not changing subdir marks. + (unless (and nosubs file (file-directory-p file)) + (subst-char-in-region (match-beginning 0) (match-end 0) old new) + (setq count (1+ count)))))))))) + (message "%d mark%s changed from `%c' to `%c'" count (dired-plural-s count) old new))) + + (defun diredp-unmark-all-marks-recursive (&optional arg details) ; `M-+ U' + "Remove ALL marks everywhere, including in marked subdirs. +A prefix arg is as for `diredp-unmark-all-files-recursive'. +Note that a negative prefix arg (e.g. `C--') removes all marks from +this Dired buffer and then does the same recursively for each of its +subdirs. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-unmark-all-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (diredp-unmark-all-files-recursive ?\r arg details)) + + (defun diredp-unmark-all-files-recursive (mark &optional arg predicate details) ; `M-+ M-DEL' + "Remove a given mark (or ALL) everywhere, including in marked subdirs. +You are prompted for the mark character to remove. If you hit `RET' +instead then ALL mark characters are removed. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +* A non-positive prefix arg means ignore subdir markings and act + instead on ALL subdirs. + +* A non-negative prefix arg means do not change marks on subdirs + themselves. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp: + Non-nil arg PREDICATE is a file-name predicate. Act on only the + files for which it returns non-nil. + DETAILS is passed to `diredp-get-subdirs'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((cursor-in-echo-area t) + (mrk (progn (message "Remove marks (RET means all): ") (read-char)))) + (list mrk current-prefix-arg nil diredp-list-file-attributes)))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (nosubs (natnump numarg)) + (ignore-marks (and numarg (<= numarg 0))) + (dired-marker-char ?\ ) ; Unmark + (sdirs (diredp-get-subdirs ignore-marks predicate details)) + (mrk-strg (format "\n%c" mark)) + (count 0) + dbufs) + (unless (char-displayable-p mark) (error "Not a displayable character: `%c'" mark)) + (if (eq mark ?\r) + (message "Unmarking ALL marks here and below...") + (message "Unmarking mark `%c' here and below..." mark)) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((inhibit-read-only t) + (file nil)) + (save-excursion + (goto-char (point-min)) + (while (if (eq mark ?\r) + (re-search-forward dired-re-mark nil t) + (search-forward mrk-strg nil t)) + (save-match-data (setq file (dired-get-filename 'no-dir t))) + ;; Do nothing if marked subdir and not changing subdir marks. + (unless (and nosubs file (file-directory-p file)) + (subst-char-in-region (match-beginning 0) (match-end 0) (preceding-char) ?\ )) + (setq count (1+ count)))))))) + (message "%d mark%s UNmarked" count (dired-plural-s count)))) + + ) + +(when (and (memq system-type '(windows-nt ms-dos)) (fboundp 'w32-browser)) + + (defun diredp-multiple-w32-browser-recursive (&optional ignore-marks-p details) + "Run Windows apps for with marked files, including those in marked subdirs. +Like `dired-multiple-w32-browser', but act recursively on subdirs. + +See `diredp-do-find-marked-files-recursive' for a description of the +files included. In particular, if no files are marked here or in a +marked subdir, then all files in the directory are included. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((files (diredp-get-files ignore-marks-p nil nil nil nil details))) + (while files + (w32-browser (car files)) + (sleep-for w32-browser-wait-time) + (setq files (cdr files))))) + + ) + +;;;###autoload +(defun diredp-copy-filename-as-kill-recursive (&optional arg details) ; Bound to `M-+ M-w' + "Copy names of marked files here and in marked subdirs, to `kill-ring'. +The names are separated by a space. + +Like `dired-copy-filename-as-kill', but act recursively on subdirs. +\(Do not copy subdir names themselves.) + +With no prefix arg, use relative file names. +With a zero prefix arg, use absolute file names. +With a plain prefix arg (`C-u'), use names relative to the current +Dired directory. (This might contain slashes if in a subdirectory.) + +If on a subdir headerline, use absolute subdir name instead - prefix +arg and marked files are ignored in this case. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +The names are copied to the kill ring and to variable +`diredp-last-copied-filenames'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((files (mapcar (cond ((zerop (prefix-numeric-value arg)) #'identity) + ((consp arg) (lambda (fn) (concat (dired-current-directory t) + (file-name-nondirectory fn)))) + (t (lambda (fn) (file-name-nondirectory fn)))) + (diredp-get-files nil nil nil nil nil details))) + (string (mapconcat #'identity files " "))) + (unless (string= "" string) + (if (eq last-command 'kill-region) (kill-append string nil) (kill-new string)) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer))) + (message "%s" string))) + +;;;###autoload +(defun diredp-copy-abs-filenames-as-kill-recursive (&optional ignore-marks-p details) ; Not bound. + "Copy absolute names of files marked here and in marked subdirs, recursively. +The names are copied to the kill ring and to variable +`dired-copy-filename-as-kill'. + +The files whose names are copied are those that are marked in the +current Dired buffer, or all files in the directory if none are +marked. Marked subdirectories are handled recursively in the same +way. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-copy-filename-as-kill-recursive'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-copy-filename-as-kill-recursive 0 details) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer))) + +;;;###autoload +(defun diredp-mark-files-regexp-recursive (regexp + &optional marker-char ignore-marks-p details) ; Bound to `M-+ % m' + "Mark all files matching REGEXP, including those in marked subdirs. +Like `dired-mark-files-regexp' but act recursively on marked subdirs. + +The file names to be matched by this command are always absolute - +they include the full directory. Note that this does NOT correspond +to the default behavior for `dired-mark-files-regexp'. The other +matching possibilities offered by `dired-mark-files-regexp' are not +available for this command. + +Directories `.' and `..' are never marked. + +A non-negative prefix arg means to UNmark the files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all matching files in this +directory and all descendant directories. + +REGEXP is an Emacs regexp, not a shell wildcard. Thus, use `\\.o$' for +object files--just `.o' will mark more than you might think. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " files (regexp): ")) + (and unmark ?\040) + ignorep + diredp-list-file-attributes))) + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char)) + (sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (matched 0) + (changed 0) + dbufs chg.mtch) + (message "%s files..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) ; Empty line + (let ((fn (dired-get-filename nil 'NO-ERROR))) + (and fn (diredp-string-match-p regexp fn)))) + "file") + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" + matched + (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +;;;###autoload +(defun diredp-mark-files-containing-regexp-recursive (regexp + &optional marker-char ignore-marks-p details) ; `M-+ % g' + "Mark files with contents containing a REGEXP match, including in marked subdirs. +Like `dired-mark-files-containing-regexp' but act recursively on +marked subdirs. + +A non-negative prefix arg means to UNmark the files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all matching files in this +directory and all descendant directories. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +If a file is visited in a buffer and `dired-always-read-filesystem' is +nil, this looks in the buffer without revisiting the file, so the +results might be inconsistent with the file on disk if its contents +have changed since it was last visited. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " files containing (regexp): ")) + (and unmark ?\040) + ignorep + diredp-list-file-attributes))) + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char)) + (sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (matched 0) + (changed 0) + dbufs chg.mtch) + (message "%s files..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch + (diredp-mark-if + (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) + (let ((fname (dired-get-filename nil t))) + + (and fname + (file-readable-p fname) + (not (file-directory-p fname)) + (let ((prebuf (get-file-buffer fname))) + (message "Checking %s" fname) + ;; For now, do it inside Emacs. Grep might be better if there are lots of files. + (if (and prebuf (or (not (boundp 'dired-always-read-filesystem)) + (not dired-always-read-filesystem))) ; Emacs 26+ + (with-current-buffer prebuf + (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t))) + (with-temp-buffer + (insert-file-contents fname) + (goto-char (point-min)) + (re-search-forward regexp nil t))))))) + "file") + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" + matched + (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +(defun diredp-mark-extension-recursive (extension &optional arg details) ; Bound to `M-+ * .' + "Mark all files with a certain EXTENSION, including in marked subdirs. +A `.' is not automatically prepended to the string entered. + +This is like `diredp-mark/unmark-extension', but this acts recursively +on marked subdirs, and a non-positive prefix arg acts differently. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +Non-interactively, EXTENSION is the extension (a string). It can also +be a list of extension strings. +Optional argument ARG is the prefix arg. + +When called from Lisp, DETAILS is passed to `diredp-mark-files-regexp-recursive'." + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " extension: ")) + current-prefix-arg + diredp-list-file-attributes))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (or (listp extension) (setq extension (list extension))) + (diredp-mark-files-regexp-recursive (concat ".+[.]\\(" + (mapconcat #'regexp-quote extension "\\|") + "\\)$") + (if unmark ?\040 dired-marker-char) + ignorep + details))) + +;; FIXME: Factor out code that is common with `dired-mark-sexp'. +;; +(when (fboundp 'minibuffer-with-setup-hook) ; Emacs 22+ + + (defun diredp-mark-sexp-recursive (predicate &optional arg details) ; Bound to `M-+ M-(', `M-+ * (' + "Mark files here and below for which PREDICATE returns non-nil. +Like `diredp-mark-sexp', but act recursively on subdirs. + +A non-negative prefix arg means to unmark those files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +PREDICATE is a lisp sexp that can refer to the following symbols as +variables: + + `mode' [string] file permission bits, e.g. \"-rw-r--r--\" + `nlink' [integer] number of links to file + `size' [integer] file size in bytes + `uid' [string] owner + `gid' [string] group (If the gid is not displayed by `ls', + this will still be set (to the same as uid)) + `time' [string] the time that `ls' displays, e.g. \"Feb 12 14:17\" + `name' [string] the name of the file + `sym' [string] if file is a symbolic link, the linked-to name, + else \"\" + `inode' [integer] the inode of the file (only for `ls -i' output) + `blks' [integer] the size of the file for `ls -s' output + (ususally in blocks or, with `-k', in Kbytes) +Examples: + Mark zero-length files: `(equal 0 size)' + Mark files last modified on Feb 2: `(string-match \"Feb 2\" time)' + Mark uncompiled Emacs Lisp files (`.el' file without a `.elc' file): + First, Dired just the source files: `dired *.el'. + Then, use \\[diredp-mark-sexp-recursive] with this sexp: + (not (file-exists-p (concat name \"c\"))) + +There's an ambiguity when a single integer not followed by a unit +prefix precedes the file mode: It is then parsed as inode number +and not as block size (this always works for GNU coreutils ls). + +Another limitation is that the uid field is needed for the +function to work correctly. In particular, the field is not +present for some values of `ls-lisp-emulation'. + +This function operates only on the Dired buffer content. It does not +refer at all to the underlying file system. Contrast this with +`find-dired', which might be preferable for the task at hand. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + ;; Using `sym' = "", instead of nil, for non-linked files avoids the trap of + ;; (string-match "foo" sym) into which a user would soon fall. + ;; Use `equal' instead of `=' in the example, as it works on integers and strings. + ;; (interactive "xMark if (vars: inode,blks,mode,nlink,uid,gid,size,time,name,sym): \nP") + + (interactive + (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0)))) + (diredp-get-confirmation-recursive) + (list (diredp-read-expression (format "%s if (Lisp expr): " (if current-prefix-arg "UNmark" "Mark"))) + current-prefix-arg + diredp-list-file-attributes))) + (message "%s" predicate) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0))) + (dired-marker-char (if unmark ?\040 dired-marker-char)) + (inode nil) + (blks ()) + (matched 0) + (changed 0) + dbufs chg.mtch mode nlink uid gid size time name sym) + (dolist (dir (cons default-directory (diredp-get-subdirs ignorep nil details))) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch + (diredp-mark-if + (save-excursion + (and + ;; Sets vars INODE BLKS MODE NLINK UID GID SIZE TIME NAME and SYM + ;; according to current file line. Returns `t' for success, nil if + ;; there is no file line. Upon success, these vars are set, to either + ;; nil or the appropriate value, so they need not be initialized. + ;; Moves point within the current line. + (dired-move-to-filename) + (let ((mode-len 10) ; Length of `mode' string. + ;; As in `dired.el', but with subexpressions \1=inode, \2=blks: + ;; GNU `ls -hs' suffixes the block count with a unit and prints it as a float + ;; FreeBSD does neither. + ;; $$$$$$ (dired-re-inode-size "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?") + (dired-re-inode-size (if (> emacs-major-version 24) + "\\=\\s *\\([0-9]+\\s +\\)?\ +\\(?:\\([0-9]+\\(?:\\.[0-9]*\\)?[BkKMGTPEZY]?\\)? ?\\)" + "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?")) + pos) + (beginning-of-line) + (forward-char 2) + (search-forward-regexp dired-re-inode-size nil t) + ;; `INODE', `BLKS', `MODE' + ;; XXX Might be a size not followed by a unit prefix. + ;; Could set `blks' to `inode' if it were otherwise nil, with similar reasoning + ;; as for setting `gid' to `uid', but it would be even more whimsical. + (setq inode (and (match-string 1) (string-to-number (match-string 1))) + blks (and (match-string 2) (if (fboundp 'dired-x--string-to-number) ; Emacs 25+ + (dired-x--string-to-number (match-string 2)) + (string-to-number (match-string 2)))) + mode (buffer-substring (point) (+ mode-len (point)))) + (forward-char mode-len) + ;; Skip any extended attributes marker ("." or "+"). + (unless (eq (char-after) ?\ ) (forward-char 1)) + (setq nlink (read (current-buffer))) ; `NLINK' + + ;; `UID' + ;; Another issue is that GNU `ls -n' right-justifies numerical UIDs and GIDs, + ;; while FreeBSD left-justifies them, so do not rely on a specific whitespace + ;; layout. Both of them right-justify all other numbers, though. + ;; XXX Return a number if the `uid' or `gid' seems to be numerical? + ;; $$$$$$ (setq uid (buffer-substring (+ (point) 1) (progn (forward-word 1) (point)))) + (setq uid (buffer-substring (progn (skip-chars-forward " \t") (point)) + (progn (skip-chars-forward "^ \t") (point)))) + (cond ((> emacs-major-version 24) + (dired-move-to-filename) + (save-excursion + (setq time ; `TIME' + ;; The regexp below tries to match from the last digit of the size + ;; field through a space after the date. Also, dates may have + ;; different formats depending on file age, so the date column need + ;; not be aligned to the right. + (buffer-substring + (save-excursion (skip-chars-backward " \t") (point)) + (progn (re-search-backward directory-listing-before-filename-regexp) + (skip-chars-forward "^ \t") + (1+ (point)))) + + size ; `SIZE' + (dired-x--string-to-number + ;; We know that there's some kind of number before point because + ;; the regexp search above succeeded. Not worth doing an extra + ;; check for leading garbage. + (buffer-substring (point) (progn (skip-chars-backward "^ \t") (point)))) + ;; If no `gid' is displayed, `gid' will be set to `uid' but user + ;; will then not reference it anyway in PREDICATE. + + gid ; `GID' + (buffer-substring (progn (skip-chars-backward " \t") (point)) + (progn (skip-chars-backward "^ \t") (point))))) + ;; `NAME', `SYM' + (setq name (buffer-substring (point) + (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) + (line-end-position)) + ""))) + (t + (re-search-forward + (if (< emacs-major-version 20) + "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\)" + dired-move-to-filename-regexp)) + (goto-char (match-beginning 1)) + (forward-char -1) + (setq size ; `SIZE' + (string-to-number (buffer-substring (save-excursion (backward-word 1) + (setq pos (point))) + (point)))) + (goto-char pos) + (backward-word 1) + ;; `GID', `TIME', `NAME', `SYM' + ;; if no `gid' is displayed, `gid' will be set to `uid' but user will then + ;; not reference it anyway in PREDICATE. + (setq gid (buffer-substring (save-excursion (forward-word 1) (point)) (point)) + time (buffer-substring (match-beginning 1) (1- (dired-move-to-filename))) + name (buffer-substring (point) (or (dired-move-to-end-of-filename t) + (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) + (line-end-position)) + ""))))) + ;; Vanilla Emacs uses `lexical-binding' = t, and it passes bindings to `eval' + ;; as a second arg. We use `lexical-binding' = nil, and anyway there should + ;; be no need to pass the bindings. + (eval predicate))) + (format "'%s file" predicate))) + (setq changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" matched (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + + (if (fboundp 'read--expression) ; Emacs 24.4+ + (defalias 'diredp-read-expression 'read--expression) + (defun diredp-read-expression (prompt &optional initial-contents) + (let ((minibuffer-completing-symbol t)) + (minibuffer-with-setup-hook + (lambda () ; Vanilla Emacs FIXME: call `emacs-lisp-mode'? + (add-function :before-until (local 'eldoc-documentation-function) + #'elisp-eldoc-documentation-function) + (eldoc-mode 1) + (add-hook 'completion-at-point-functions #'elisp-completion-at-point nil t) + (run-hooks 'eval-expression-minibuffer-setup-hook)) + (read-from-minibuffer + prompt initial-contents (if (boundp 'pp-read-expression-map) + pp-read-expression-map + read-expression-map) + t 'read-expression-history))))) + + ) + +;;;###autoload +(defun diredp-mark-autofiles-recursive (&optional arg details) ; Bound to `M-+ * B' + "Mark all autofiles, including in marked subdirs. +Autofiles are files that have an autofile bookmark. +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-recursive-1 arg "autofiles" "autofile" + '(and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (and fname (bmkp-get-autofile-bookmark fname)))) + details)) + +;;;###autoload +(defun diredp-mark-executables-recursive (&optional arg details) ; Bound to `M-+ * *' + "Mark all executable files, including in marked subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "executable files" "executable file" '(diredp-looking-at-p dired-re-exe) details)) + +;;;###autoload +(defun diredp-mark-directories-recursive (&optional arg details) ; Bound to `M-+ * /' + "Mark all directories except `.' and `..', including in marked subdirs. +The directories included are those that are marked in the current +Dired buffer, or all subdirs in the directory if none are marked. +Marked subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "directories" "directory" '(and (diredp-looking-at-p dired-re-dir) + (not (diredp-looking-at-p dired-re-dot))) + details)) +;;;###autoload +(defun diredp-mark-symlinks-recursive (&optional arg details) ; Bound to `M-+ * @' + "Mark all symbolic links, including in marked subdirs. +The symlinks included are those that are marked in the current Dired +buffer, or all symlinks in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-subdirs'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "symlinks" "symbolic link" '(diredp-looking-at-p dired-re-sym) details)) + +(defun diredp-mark-recursive-1 (arg plural singular predicate-sexp details) + "Helper for `diredp-mark-*-recursive' commands." + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0))) + (dired-marker-char (if unmark ?\040 dired-marker-char)) + (sdirs (diredp-get-subdirs ignorep nil details)) + (changed 0) + (matched 0) + dbufs chg.mtch) + (message "%s %s..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking") plural) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch (diredp-mark-if (eval predicate-sexp) singular) + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s %s%s%s newly %s" + matched + (if (= 1 matched) singular plural) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +;;;###autoload +(defun diredp-capitalize-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % c' + "Rename marked files, including in marked subdirs, by capitalizing them. +Like `diredp-capitalize', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'capitalize "Rename by capitalizing:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-upcase-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % u' + "Rename marked files, including in marked subdirs, making them uppercase. +Like `dired-upcase', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'upcase "Rename to uppercase:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-downcase-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % l' + "Rename marked files, including in marked subdirs, making them lowercase. +Like `dired-downcase', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'downcase "Rename to lowercase:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-apply-function-recursive (function &optional arg details) ; Bound to `M-+ @' + "Apply FUNCTION to the marked files. +Like `diredp-do-apply-function' but act recursively on subdirs and do +no result or error logging or echoing. + +The files acted on are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a plain prefix ARG (`C-u'), visit each file and invoke FUNCTION + with no arguments. +Otherwise, apply FUNCTION to each file name. + +Any other prefix arg behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list (read (completing-read "Function: " obarray 'functionp nil nil + (and (boundp 'function-name-history) 'function-name-history))) + current-prefix-arg + diredp-list-file-attributes))) + (if (and (consp arg) (< (car arg) 16)) + (dolist (file (diredp-get-files)) (with-current-buffer (find-file-noselect file) (funcall function))) + (dolist (file (diredp-get-files arg nil nil nil nil details)) (funcall function file)))) + +;;;###autoload +(defun diredp-do-delete-recursive (arg &optional details) ; Bound to `M-+ D' + "Delete marked (not flagged) files, including in marked subdirs. +Like `dired-do-delete' but act recursively on subdirs. + +The files to be deleted are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files' and `diredp-get-subdirs'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (unless arg + (ding) + (message "NOTE: Deletion of files marked `%c' (not those flagged `%c')." + dired-marker-char dired-del-marker)) + (let* ((files (diredp-get-files nil nil nil nil 'ONLY-MARKED-P details)) + (count (length files)) + (trashing (and (boundp 'delete-by-moving-to-trash) delete-by-moving-to-trash)) + (succ 0)) + (if (dired-mark-pop-up + " *Deletions*" 'delete files dired-deletion-confirmer + (format "%s %s " (if trashing "Trash" "Delete") (dired-mark-prompt arg files))) + (let ((progress-reporter (and (fboundp 'make-progress-reporter) + (make-progress-reporter (if trashing "Trashing..." "Deleting...") + succ + count))) + (failures ())) + (unless progress-reporter (message "Deleting...")) + (dolist (file files) + (condition-case err + (progn (if (fboundp 'dired-delete-file) ; Emacs 22+ + (dired-delete-file file dired-recursive-deletes trashing) + ;; This test is equivalent to (and (file-directory-p file) (not (file-symlink-p file))) + ;; but more efficient. + (if (eq t (car (file-attributes file))) (delete-directory file) (delete-file file))) + (setq succ (1+ succ)) + (when (fboundp 'progress-reporter-update) + (progress-reporter-update progress-reporter succ))) + (error (dired-log "%s\n" err) ; Catch errors from failed deletions. + (setq failures (cons file failures)))) + (dired-clean-up-after-deletion file)) + (if failures + (dired-log-summary (format "%d of %d deletion%s failed" + (length failures) count (dired-plural-s count)) + failures) + (if (fboundp 'progress-reporter-done) + (progress-reporter-done progress-reporter) + (message "Deleting...done"))) + (let ((sdirs (diredp-get-subdirs nil nil details)) + dbufs) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) (dired-revert)))))) + (message "OK. NO deletions performed")))) + +;;;###autoload +(defun diredp-do-move-recursive (&optional ignore-marks-p details) ; Bound to `M-+ R' + "Move marked files, including in marked subdirs, to a given directory. +Like `dired-do-rename', but act recursively on subdirs to pick up the +files to move. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +This means move the marked files of marked subdirs and their marked +subdirs, etc. It does not mean move or rename the subdirs themselves +recursively. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Renames any buffers that are visiting the files. + +The default suggested for the target directory depends on the value of +`dired-dwim-target', which see." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-rename-file "Move" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-copy-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C' + "Copy marked files, including in marked subdirs, to a given directory. +Like `dired-do-copy', but act recursively on subdirs to pick up the +files to copy. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +This means copy the marked files of marked subdirs and their marked +subdirs, etc. It does not mean copy the subdirs themselves +recursively. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Preserves the last-modified date when copying, unless +`dired-copy-preserve-time' is nil. + +The default suggested for the target directory depends on the value of +`dired-dwim-target', which see. + +This command copies symbolic links by creating new ones, like UNIX +command `cp -d'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let ((dired-recursive-copies nil)) ; Doesn't have to be nil, but let's not go overboard now. + (diredp-do-create-files-recursive #'dired-copy-file "Copy" ignore-marks-p details))) + +(defun diredp-do-create-files-recursive (file-creator operation ignore-marks-p &optional details) + "Create a new file for each marked file, including those in marked subdirs. +Like `dired-do-create-files', but act recursively on subdirs, and +always keep markings. +Prompts for the target directory, in which to create the files. +FILE-CREATOR and OPERATION are as in `dired-create-files'. +Non-nil IGNORE-MARKS-P means ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (lexical-let* ((fn-list (diredp-get-files ignore-marks-p nil nil nil nil details)) + (target-dir (dired-dwim-target-directory)) + (defaults (and (fboundp 'dired-dwim-target-defaults) ; Emacs 23+ + (dired-dwim-target-defaults fn-list target-dir))) + (target (expand-file-name + (if (fboundp 'minibuffer-with-setup-hook) ; Emacs 22+ + (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + nil) + (setq minibuffer-default defaults)) + (funcall (if (fboundp 'read-directory-name) + #'read-directory-name + #'read-file-name) + (concat operation " files to: ") + default-directory default-directory)) + (funcall (if (fboundp 'read-directory-name) + #'read-directory-name + #'read-file-name) + (concat operation "files to: ") + default-directory default-directory))))) + (unless (file-directory-p target) (error "Target is not a directory: `%s'" target)) + (dired-create-files + file-creator operation fn-list + #'(lambda (from) (expand-file-name (file-name-nondirectory from) target)) + ;; Hard-code `*' marker, or else it will be removed in lower dirs because the code uses + ;; `dired-file-marker', which only works in the current Dired directory. + ?*))) + +(defun diredp-create-files-non-directory-recursive (file-creator basename-constructor operation + &optional ignore-marks-p details) + "Apply FILE-CREATOR + BASENAME-CONSTRUCTOR to non-dir part of marked names. +Like `dired-create-files-non-directory', but act recursively on subdirs. + +The files acted on are those marked in the current Dired buffer, or +all files in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +With non-nil IGNORE-MARKS-P, ignore all marks - include all files in +this Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (let (rename-non-directory-query) + (dired-create-files + file-creator + operation + (diredp-get-files ignore-marks-p nil nil nil nil details) + #'(lambda (from) + (let ((to (concat (file-name-directory from) + (funcall basename-constructor (file-name-nondirectory from))))) + (and (let ((help-form (format "\ +Type SPC or `y' to %s one file, DEL or `n' to skip to next, +`!' to %s all remaining matches with no more questions." + (downcase operation) + (downcase operation)))) + (dired-query 'rename-non-directory-query (concat operation " `%s' to `%s'") + (dired-make-relative from) (dired-make-relative to))) + to))) + ;; Hard-code `*' marker, or else it will be removed in lower dirs because the code uses + ;; `dired-file-marker', which only works in the current Dired directory. + ?*))) + +(defun diredp-do-chxxx-recursive (attribute-name program op-symbol &optional ignore-marks-p default details) + "Change attributes of the marked files, including those in marked subdirs. +Refresh their file lines. + +Like `dired-do-chxxx', but act recursively on subdirs. The subdirs +acted on are those that are marked in the current Dired buffer, or all +subdirs in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +ATTRIBUTE-NAME is a string describing the attribute to the user. +PROGRAM is the program used to change the attribute. +OP-SYMBOL is the type of operation (for use in `dired-mark-pop-up'). +Non-nil IGNORE-MARKS-P means ignore all marks - include all files in this + Dired buffer and all subdirs, recursively. +DEFAULT is the default value for reading the mark string. +DETAILS is passed to `diredp-get-files' and + `diredp-do-redisplay-recursive'." + (let* ((this-buff (current-buffer)) + (files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (prompt (concat "Change " attribute-name " of %s to: ")) + (new-attribute (if (> emacs-major-version 22) + (dired-mark-read-string prompt nil op-symbol ignore-marks-p files default) + (dired-mark-read-string prompt nil op-symbol ignore-marks-p files))) + (operation (concat program " " new-attribute)) + failures) + (setq failures (dired-bunch-files 10000 (function dired-check-process) + (append (list operation program) + (unless (string-equal new-attribute "") + (if (equal attribute-name "Timestamp") + (list "-t" new-attribute) + (list new-attribute))) + (and (diredp-string-match-p "gnu" system-configuration) + '("--"))) ; -------------------------------- + files)) + (with-current-buffer this-buff (diredp-do-redisplay-recursive details 'MSGP)) + (when failures (dired-log-summary (format "%s: error" operation) nil)))) + +;;;###autoload +(defun diredp-do-chmod-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M' + "Change the mode of the marked files, including those in marked subdirs. +Symbolic modes like `g+w' are allowed. + +Note that marked subdirs are not changed. Their markings are used only +to indicate that some of their files are to be changed. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files' and `diredp-do-redisplay-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (modestr (and (stringp (car files)) (nth 8 (file-attributes (car files))))) + (default (and (stringp modestr) + (string-match "^.\\(...\\)\\(...\\)\\(...\\)$" modestr) + (replace-regexp-in-string "-" "" (format "u=%s,g=%s,o=%s" + (match-string 1 modestr) + (match-string 2 modestr) + (match-string 3 modestr))))) + (modes (if (> emacs-major-version 22) + (dired-mark-read-string + "Change mode of marked files here and below to: " nil 'chmod + nil files default) + (dired-mark-read-string + "Change mode of marked files here and below to: " nil 'chmod + nil files)))) + (when (equal modes "") (error "No file mode specified")) + (dolist (file files) + (set-file-modes file (or (and (diredp-string-match-p "^[0-7]+" modes) (string-to-number modes 8)) + (file-modes-symbolic-to-number modes (file-modes file))))) + (diredp-do-redisplay-recursive details 'MSGP))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-do-chgrp-recursive (&optional ignore-marks-p details) + "Change the group of the marked (or next ARG) files. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-do-chxxx-recursive "Group" "chgrp" 'chgrp ignore-marks-p nil details))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-do-chown-recursive (&optional ignore-marks-p details) + "Change the owner of the marked (or next ARG) files. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-do-chxxx-recursive "Owner" dired-chown-program 'chown ignore-marks-p nil details))) + +;;;###autoload +(defun diredp-do-touch-recursive (&optional ignore-marks-p details) + "Change the timestamp of marked files, including those in marked subdirs. +This calls `touch'. Like `dired-do-touch', but act recursively on +subdirs. The subdirs inserted are those that are marked in the +current Dired buffer, or all subdirs in the directory if none are +marked. Marked subdirectories are handled recursively in the same +way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-chxxx-recursive "Timestamp" (if (boundp 'dired-touch-program) + dired-touch-program ; Emacs 22+ + "touch") + 'touch + ignore-marks-p + (format-time-string "%Y%m%d%H%M.%S" (current-time)) + details)) + +;;;###autoload +(defun diredp-do-redisplay-recursive (&optional details msgp) + "Redisplay marked file lines, including those in marked subdirs. +Non-nil MSGP means show status messages. +Like `dired-do-redisplay' with no args, but act recursively on +subdirs. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-ensure-mode) + (unless (y-or-n-p "Act on all marked file lines in and UNDER this dir? ") + (error "OK, canceled")) + (list diredp-list-file-attributes t))) + (when msgp (message "Redisplaying...")) + (dolist (dir (cons default-directory + (diredp-get-files nil #'file-directory-p 'INCLUDE-SUBDIRS 'DONT-ASK nil details))) + (with-current-buffer (dired-noselect dir) + ;; `message' is much faster than making `dired-map-over-marks' show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks + (let ((fname (dired-get-filename)) + ;; Postpone readin hook till we map over all marked files (Bug#6810). + (dired-after-readin-hook nil)) + (message "Redisplaying... %s" fname) + (dired-update-file-line fname)) + nil) + (run-hooks 'dired-after-readin-hook) + (dired-move-to-filename))) + (when msgp (message "Redisplaying...done"))) + + +;;; `diredp-marked(-other-window)' tries to treat SWITCHES, but SWITCHES seems to be ignored +;;; by `dired' when the DIRNAME arg is a cons, at least on MS Windows. I filed Emacs bug #952 +;;; on 2008-09-10, but this doesn't work in Emacs 20, 21, 22, or 23, so I don't know if it will +;;; ever be fixed. If it is declared a non-bug and it doesn't work on any platforms, then I'll +;;; remove SWITCHES here, alas. + +;;;###autoload +(defun diredp-marked (dirname &optional n switches) ; Not bound + "Open Dired on only the marked files or the next N files. +With a non-zero numeric prefix arg N, use the next abs(N) files. +A plain (`C-u'), zero, or negative prefix arg prompts for listing +switches as in command `dired'. + +Note that the marked files can include files in inserted +subdirectories, so the Dired buffer that is opened can contain files +from multiple directories in the same tree." + (interactive (progn (diredp-ensure-mode) + (let ((num (and current-prefix-arg + (atom current-prefix-arg) + (not (zerop (prefix-numeric-value current-prefix-arg))) + (abs (prefix-numeric-value current-prefix-arg))))) + (list (cons (generate-new-buffer-name (buffer-name)) (dired-get-marked-files t num)) + num + (and current-prefix-arg ; Switches + (or (consp current-prefix-arg) + (< (prefix-numeric-value current-prefix-arg) 0)) + (read-string "Dired listing switches: " dired-listing-switches)))))) + (unless (or n (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t)))) + (error "No marked files")) + (dired dirname switches)) + +;;;###autoload +(defun diredp-marked-other-window (dirname &optional n switches) ; Bound to `C-M-*' + "Same as `diredp-marked', but uses a different window." + (interactive (progn (diredp-ensure-mode) + (let ((num (and current-prefix-arg + (atom current-prefix-arg) + (not (zerop (prefix-numeric-value current-prefix-arg))) + (abs (prefix-numeric-value current-prefix-arg))))) + (list (cons (generate-new-buffer-name (buffer-name)) (dired-get-marked-files t num)) + num + (and current-prefix-arg ; Switches + (or (consp current-prefix-arg) + (< (prefix-numeric-value current-prefix-arg) 0)) + (read-string "Dired listing switches: " dired-listing-switches)))))) + (unless (or n (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t)))) + (error "No marked files")) + (dired-other-window dirname switches)) + + +;; Similar to `dired-mark-extension' in `dired-x.el'. +;; The difference is that this uses prefix arg to unmark, not to determine the mark character. +;;;###autoload +(defun diredp-mark/unmark-extension (extension &optional unmark-p) ; Bound to `* .' + "Mark all files with a certain EXTENSION for use in later commands. +A `.' is not automatically prepended to the string entered. +Non-nil prefix argument UNMARK-P means unmark instead of mark. + +Non-interactively, EXTENSION is the extension (a string). It can also + be a list of extension strings. +Optional argument UNMARK-P is the prefix arg." + (interactive (list (diredp-read-regexp (concat (if current-prefix-arg "UNmark" "Mark") "ing extension: ")) + current-prefix-arg)) + (or (listp extension) (setq extension (list extension))) + (dired-mark-files-regexp (concat ".";; Do not match names with nothing but an extension + "\\(" + (mapconcat #'regexp-quote extension "\\|") + "\\)$") + (and current-prefix-arg ?\040))) + +(defun diredp-mark-files-tagged-all/none (tags &optional none-p unmarkp prefix) + "Mark or unmark files tagged with all or none of TAGS. +TAGS is a list of strings, the tag names. +NONEP non-nil means mark/unmark files that have none of the TAGS. +UNMARKP non-nil means unmark; nil means mark. +PREFIX non-nil is the prefix of the autofile bookmarks to check. + +As a special case, if TAGS is empty, then mark or unmark the files +that have any tags at all, or if NONEP is non-nil then mark or unmark +those that have no tags at all." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let* ((fname (dired-get-filename nil t)) + (bmk (and fname (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (presentp nil) + (allp (and btgs (catch 'diredp-m-f-t-an + (dolist (tag tags) + (setq presentp (assoc-default tag btgs nil t)) + (unless (if none-p (not presentp) presentp) + (throw 'diredp-m-f-t-an nil))) + t)))) + (if (null tags) + (if none-p (not btgs) btgs) + allp))) + (if none-p "no-tags-matching file" "all-tags-matching file")))) + +(defun diredp-mark-files-tagged-some/not-all (tags &optional notallp unmarkp prefix) + "Mark or unmark files tagged with any or not all of TAGS. +TAGS is a list of strings, the tag names. +NOTALLP non-nil means mark/unmark files that do not have all TAGS. +UNMARKP non-nil means unmark; nil means mark. +PREFIX non-nil is the prefix of the autofile bookmarks to check. + +As a special case, if TAGS is empty, then mark or unmark the files +that have any tags at all, or if NOTALLP is non-nil then mark or +unmark those that have no tags at all." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let* ((fname (dired-get-filename nil t)) + (bmk (and fname + (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (presentp nil) + (allp (and btgs (catch 'diredp-m-f-t-sna + (dolist (tag tags) + (setq presentp (assoc-default tag btgs nil t)) + (when (if notallp (not presentp) presentp) + (throw 'diredp-m-f-t-sna t))) + nil)))) + (if (null tags) (if notallp (not btgs) btgs) allp))) + (if notallp "some-tags-not-matching file" "some-tags-matching file")))) + +;;;###autoload +(defun diredp-mark-files-tagged-all (tags &optional none-p prefix) ; `T m *' + "Mark all files that are tagged with *each* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, mark all that are *not* tagged with *any* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags none-p nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-none (tags &optional allp prefix) ; `T m ~ +' + "Mark all files that are not tagged with *any* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + no tags at all. +With a prefix arg, mark all that are tagged with *each* tag in TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags (not allp) nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-some (tags &optional somenotp prefix) ; `T m +' + "Mark all files that are tagged with *some* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, mark all that are *not* tagged with *all* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags somenotp nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-not-all (tags &optional somep prefix) ; `T m ~ *' + "Mark all files that are not tagged with *all* TAGS. +As a special case, if TAGS is empty, then mark the files that have + no tags at all. +With a prefix arg, mark all that are tagged with *some* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags (not somep) nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-regexp (regexp &optional notp prefix) ; `T m %' + "Mark files that have at least one tag that matches REGEXP. +With a prefix arg, mark all that are tagged but have no matching tags. +You need library `bookmark+.el' to use this command." + (interactive (list (read-string "Regexp: ") + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (lexical-let* ((fname (dired-get-filename nil t)) + (bmk (and fname + (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (anyp (and btgs (bmkp-some #'(lambda (tag) + (diredp-string-match-p + regexp + (bmkp-tag-name tag))) + btgs)))) + (and btgs (if notp (not anyp) anyp)))) + "some-tag-matching-regexp file")) + +;;;###autoload +(defun diredp-unmark-files-tagged-regexp (regexp &optional notp prefix) ; `T u %' + "Unmark files that have at least one tag that matches REGEXP. +With a prefix arg, unmark all that are tagged but have no matching tags. +You need library `bookmark+.el' to use this command." + (interactive (list (read-string "Regexp: ") + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let ((dired-marker-char ?\040)) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (lexical-let* ((fname (dired-get-filename nil t)) + (bmk (and fname (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (anyp (and btgs (bmkp-some #'(lambda (tag) + (diredp-string-match-p + regexp + (bmkp-tag-name tag))) + btgs)))) + (and btgs (if notp (not anyp) anyp)))) + "some-tag-matching-regexp file"))) + +;;;###autoload +(defun diredp-unmark-files-tagged-all (tags &optional none-p prefix) ; `T u *' + "Unmark all files that are tagged with *each* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, unmark all that are *not* tagged with *any* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags none-p 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-none (tags &optional allp prefix) ; `T u ~ +' + "Unmark all files that are *not* tagged with *any* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + no tags at all. +With a prefix arg, unmark all that are tagged with *each* tag in TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags (not allp) 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-some (tags &optional somenotp prefix) ; `T u +' + "Unmark all files that are tagged with *some* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + any tags at all. +With a prefix arg, unmark all that are *not* tagged with *all* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags somenotp 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-not-all (tags &optional somep prefix) ; `T u ~ *' + "Unmark all files that are *not* tagged with *all* TAGS. +As a special case, if TAGS is empty, then unmark the files that have + no tags at all. +With a prefix arg, unmark all that are tagged with *some* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags (not somep) 'UNMARK prefix)) + +;;;###autoload +(defun diredp-do-tag (tags &optional prefix arg) ; `T > +' + "Tag the marked (or the next prefix argument) files. +You need library `bookmark+.el' to use this command. + +Hit `RET' to enter each tag, then hit `RET' again after the last tag. +You can use completion to enter each tag. Completion is lax: you are +not limited to existing tags. + +TAGS is a list of strings. PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) #'(lambda () (diredp-tag tags pref))) + arg 'tag (diredp-fewer-than-2-files-p arg))) + +(defun diredp-tag (tags &optional prefix) + "Add tags to the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-add-tags file tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-tag (event) ; Not bound + "In Dired, add some tags to this file. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-tag (bmkp-read-tags-completing) prefix)) + 1 'tag t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-untag (tags &optional prefix arg) ; `T > -' + "Remove some tags from the marked (or the next prefix arg) files. +You need library `bookmark+.el' to use this command. + +Hit `RET' to enter each tag, then hit `RET' again after the last tag. +You can use completion to enter each tag. Completion is lax: you are +not limited to existing tags. + +TAGS is a list of strings. PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-untag tags pref))) + arg 'untag (diredp-fewer-than-2-files-p arg))) + +(defun diredp-untag (tags &optional prefix) + "Remove some tags from the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-remove-tags file tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-untag (event) ; Not bound + "In Dired, remove some tags from this file. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (lexical-let* ((bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil prefix)) + (btgs (and bmk (bmkp-get-tags bmk)))) + (unless btgs (error "File has no tags to remove")) + (dired-map-over-marks-check + #'(lambda () (diredp-untag (bmkp-read-tags-completing btgs) prefix)) 1 'untag t))) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-remove-all-tags (&optional prefix arg) ; `T > 0' + "Remove all tags from the marked (or the next prefix arg) files. +You need library `bookmark+.el' to use this command. + +PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (lexical-let ((pref prefix)) + (dired-map-over-marks-check #'(lambda () (diredp-remove-all-tags pref)) arg 'remove-all-tags + (diredp-fewer-than-2-files-p arg)))) + +(defun diredp-remove-all-tags (&optional prefix) + "Remove all tags from the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-remove-all-tags (bmkp-autofile-set file nil prefix)) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-remove-all-tags (event) ; Not bound + "In Dired, remove all tags from the marked (or next prefix arg) files. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-remove-all-tags prefix)) + 1 'remove-all-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-paste-add-tags (&optional prefix arg) ; `T > p', `T > C-y' + "Add previously copied tags to the marked (or next prefix arg) files. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-paste-add-tags pref))) + arg 'paste-add-tags + (diredp-fewer-than-2-files-p arg))) + +(defun diredp-paste-add-tags (&optional prefix) + "Add previously copied tags to the file or directory on the current line. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-add-tags file bmkp-copied-tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-paste-add-tags (event) ; Not bound + "In Dired, add previously copied tags to this file. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-paste-add-tags prefix)) + 1 'paste-add-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-paste-replace-tags (&optional prefix arg) ; `T > q' + "Replace tags for marked (or next prefix arg) files with copied tags. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-paste-replace-tags pref))) + arg 'paste-replace-tags (diredp-fewer-than-2-files-p arg))) + +(defun diredp-paste-replace-tags (&optional prefix) + "Replace tags for this file or dir with tags copied previously. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (progn (bmkp-remove-all-tags (bmkp-autofile-set file nil prefix)) + (bmkp-autofile-add-tags file bmkp-copied-tags nil prefix)) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) + +;;;###autoload +(defun diredp-mouse-do-paste-replace-tags (event) ; Not bound + "In Dired, replace tags for this file with tags copied previously. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-paste-replace-tags prefix)) + 1 'paste-replace-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-set-tag-value (tag value &optional prefix arg) ; `T > v' + "Set TAG value to VALUE, for the marked (or next prefix arg) files. +This does not change the TAG name. +You need library `bookmark+.el' to use this command. + +PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tag-completing) + (read (read-string "Value: ")) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((tg tag) + (val value) + (pref prefix)) + #'(lambda () (diredp-set-tag-value tg val pref))) + arg 'set-tag-value (diredp-fewer-than-2-files-p arg))) + +(defun diredp-set-tag-value (tag value &optional prefix) + "Set TAG value to VALUE for this file or directory. +This does not change the TAG name. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-set-tag-value (bmkp-autofile-set file nil prefix) tag value) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-set-tag-value (event) ; Not bound + "In Dired, set the value of a tag for this file. +This does not change the tag name. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-set-tag-value (bmkp-read-tag-completing) + (read (read-string "Value: ")) + prefix)) + 1 'set-tag-value t)) + (diredp-previous-line 1)) + + +;; Define these even if `Bookmark+' is not loaded. +;;;###autoload +(defun diredp-mark-autofiles () ; Bound to `* B' + "Mark all autofiles, that is, files that have an autofile bookmark." + (interactive) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark/unmark-autofiles)) + +;;;###autoload +(defun diredp-unmark-autofiles () + "Unmark all autofiles, that is, files that have an autofile bookmark." + (interactive) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark/unmark-autofiles t)) + +;;;###autoload +(defun diredp-mark/unmark-autofiles (&optional unmarkp) + "Mark all autofiles, or unmark if UNMARKP is non-nil." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (and fname (bmkp-get-autofile-bookmark fname)))) + "autofile"))) + +(when (and (fboundp 'bmkp-get-autofile-bookmark) ; Defined in `bookmark+-1.el'. + (fboundp 'hlt-highlight-region)) ; Defined in `highlight.el'. + + (defun diredp-highlight-autofiles () + "Highlight files that are autofile bookmarks. +Highlighting uses face `diredp-autofile-name'." + (save-excursion + (goto-char (point-min)) + (while (re-search-forward dired-move-to-filename-regexp nil t) + ;; If Dired details are hidden the match data gets changed. + (let* ((bmk (save-match-data + (bmkp-get-autofile-bookmark (buffer-substring (match-end 0) (line-end-position))))) + (tags (and bmk (bmkp-get-tags bmk)))) + (when bmk + (hlt-highlight-region (match-end 0) (line-end-position) + (if tags + 'diredp-tagged-autofile-name + 'diredp-autofile-name))))))) + + (cond ((fboundp 'define-minor-mode) + ;; Emacs 21+. Use `eval' so that even if the library is byte-compiled with Emacs 20, + ;; loading it into Emacs 21+ will define variable `diredp-highlight-autofiles-mode'. + (eval '(define-minor-mode diredp-highlight-autofiles-mode + "Toggle automatic highlighting of autofile bookmarks. +When you turn this on, it ensures that your bookmark file is loaded. + +NOTE: This mode is ON BY DEFAULT. More precisely, when `dired+.el' is +loaded (for the first time per Emacs session), the mode is turned ON. +To prevent this and have the mode OFF by default, you must do one of +the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize option `diredp-highlight-autofiles-mode' to `nil', AND + ensure that your `custom-file' (or the `custom-saved-variables' + part of your init file) is evaluated before `dired+.el' is loaded. + +You need libraries `Bookmark and `highlight.el' for this command." + :init-value t :global t :group 'Dired-Plus :require 'dired+ + (if (not diredp-highlight-autofiles-mode) + (remove-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (add-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (bookmark-maybe-load-default-file)) + (when (derived-mode-p 'dired-mode) (dired-revert nil nil)) + (when (interactive-p) + (message "Dired highlighting of autofile bookmarks is now %s" + (if diredp-highlight-autofiles-mode "ON" "OFF")))))) + (t;; Emacs 20. + (defun diredp-highlight-autofiles-mode (&optional arg) + "Toggle automatic highlighting of autofile bookmarks. +When you turn this on, it ensures that your bookmark file is loaded. + +NOTE: This mode is ON BY DEFAULT. More precisely, when `dired+.el' is +loaded (for the first time per Emacs session), the mode is turned ON. +To prevent this and have the mode OFF by default, you must do one of +the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize option `diredp-highlight-autofiles-mode' to `nil', AND + ensure that your `custom-file' (or the `custom-saved-variables' + part of your init file) is evaluated before `dired+.el' is loaded. + +You need libraries `Bookmark and `highlight.el' for this command." + (interactive (list (or current-prefix-arg 'toggle))) + (setq diredp-highlight-autofiles-mode (if (eq arg 'toggle) + (not diredp-highlight-autofiles-mode) + (> (prefix-numeric-value arg) 0))) + (if (not diredp-highlight-autofiles-mode) + (remove-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (add-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (bookmark-maybe-load-default-file)) + (when (derived-mode-p 'dired-mode) (dired-revert nil nil)) + (when (interactive-p) (message "Dired highlighting of autofile bookmarks is now %s" + (if diredp-highlight-autofiles-mode "ON" "OFF")))))) + + ;; Turn it ON BY DEFAULT. + (unless (or (boundp 'diredp-loaded-p) (get 'diredp-highlight-autofiles-mode 'saved-value)) + (diredp-highlight-autofiles-mode 1)) + ) + +;;;###autoload +(defun diredp-do-bookmark (&optional prefix arg) ; Bound to `M-b' + "Bookmark the marked (or the next prefix argument) files. +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for the PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. +The bookmarked position is the beginning of the file. +If you use library `bookmark+.el' then the bookmark is an autofile. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-bookmark pref nil 'NO-MSG-P))) + arg 'bookmark (diredp-fewer-than-2-files-p arg))) + +;;;###autoload +(defun diredp-mouse-do-bookmark (event) ; Not bound + "In Dired, bookmark this file. See `diredp-do-bookmark'." + (interactive "e") + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-bookmark prefix nil)) nil 'bookmark t)) + (diredp-previous-line 1)) + +(defun diredp-bookmark (&optional prefix file no-msg-p) + "Bookmark the file or directory FILE. +If you use library `bookmark+.el' then the bookmark is an autofile. +Return nil for success or the file name otherwise. + +The bookmark name is the (non-directory) file name, prefixed by + optional arg PREFIX (a string) if non-nil. + +FILE defaults to the file name on the current Dired line. + +Non-nil optional arg NO-MSG-P means do not show progress messages." + (bookmark-maybe-load-default-file) + (let ((fil (or file (dired-get-file-for-visit))) + (failure nil)) + (condition-case err + (if (fboundp 'bmkp-autofile-set) ; Bookmark+ - just set an autofile bookmark. + (bmkp-autofile-set fil nil prefix nil (not no-msg-p)) + ;; Vanilla `bookmark.el' (or very old Bookmark+ version). + (let ((bookmark-make-record-function + (cond ((and (require 'image nil t) (require 'image-mode nil t) + (condition-case nil (image-type fil) (error nil))) + ;; Last two lines of function are from `image-bookmark-make-record'. + ;; But don't use that directly, because it uses + ;; `bookmark-make-record-default', which gets nil for `filename'. + (lambda () + `((filename . ,fil) + (position . 0) + ;; NEED to keep this part of code sync'd with `bmkp-make-record-for-target-file'. + (image-type . ,(image-type fil)) + (handler . image-bookmark-jump)))) ; In `image-mode.el'. + (t + (lambda () + `((filename . ,fil) + (position . 0))))))) + (bookmark-store (concat prefix (file-name-nondirectory fil)) (cdr (bookmark-make-record)) nil))) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (if (fboundp 'bmkp-autofile-set) + (dired-log failure) + (dired-log "Failed to create bookmark for `%s':\n%s\n" fil failure)) + (dired-make-relative fil)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-set-bookmark-file-bookmark-for-marked (bookmark-file ; Bound to `C-M-b' + &optional prefix arg) + "Bookmark the marked files and create a bookmark-file bookmark for them. +The bookmarked position is the beginning of the file. +Jumping to the bookmark-file bookmark loads the set of file bookmarks. +You need library `bookmark+.el' to use this command. + +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories. + +You are also prompted for the bookmark file, BOOKMARK-FILE. The +default is `.emacs.bmk' in the current directory, but you can enter +any file name, anywhere. + +The marked-file bookmarks are added to file BOOKMARK-FILE, but this +command does not make BOOKMARK-FILE the current bookmark file. To +make it current, just jump to the bookmark-file bookmark created by +this command. That bookmark (which bookmarks BOOKMARK-FILE) is +defined in that current bookmark file. + +Example: + + Bookmark file `~/.emacs.bmk' is current before invoking this command. + The current (Dired) directory is `/foo/bar'. + The marked files are bookmarked in the (possibly new) bookmark file + `/foo/bar/.emacs.bmk'. + The bookmarks for the marked files have names prefixed by `FOOBAR '. + The name of the bookmark-file bookmark is `Foobar Files'. + Bookmark `Foobar Files' is itself in bookmark file `~/.emacs.bmk'. + Bookmark file `~/.emacs.bmk' is current after invoking this command. + +You are prompted for the name of the bookmark-file bookmark, the +BOOKMARK-FILE for the marked-file bookmarks, and a PREFIX string for +each of the marked-file bookmarks. + +See also command `diredp-do-bookmark-in-bookmark-file'." + (interactive (diredp-read-bookmark-file-args)) + (diredp-ensure-bookmark+) + (diredp-do-bookmark-in-bookmark-file bookmark-file prefix arg 'CREATE-BOOKMARK-FILE-BOOKMARK)) + +;;;###autoload +(defun diredp-do-bookmark-in-bookmark-file (bookmark-file ; Bound to `C-M-B' (aka `C-M-S-b') + &optional prefix arg bfile-bookmarkp files) + "Bookmark marked files in BOOKMARK-FILE and save BOOKMARK-FILE. +The files bookmarked are the marked files, by default. +The bookmarked position is the beginning of the file. +You are prompted for BOOKMARK-FILE. The default is `.emacs.bmk' in +the current directory, but you can enter any file name, anywhere. +You need library `bookmark+.el' to use this command. + +The marked files are bookmarked in file BOOKMARK-FILE, but this +command does not make BOOKMARK-FILE the current bookmark file. To +make it current, use `\\[bmkp-switch-bookmark-file]' (`bmkp-switch-bookmark-file'). + +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. + +Interactively, a prefix argument ARG specifies the files to use +instead of those marked. + + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories. + +See also command `diredp-set-bookmark-file-bookmark-for-marked'. + +Non-interactively: + + * Non-nil BFILE-BOOKMARKP means create a bookmark-file bookmark for + BOOKMARK-FILE. + * Non-nil FILES is the list of files to bookmark." + (interactive (diredp-read-bookmark-file-args)) + (diredp-ensure-bookmark+) + (let ((bfile-exists-p (file-readable-p bookmark-file))) + (unless bfile-exists-p (bmkp-empty-file bookmark-file)) + (unless bmkp-current-bookmark-file (setq bmkp-current-bookmark-file bookmark-default-file)) + (let ((old-bmkp-current-bookmark-file bmkp-current-bookmark-file)) + (unwind-protect + (progn (bmkp-switch-bookmark-file bookmark-file) ; Changes `*-current-bookmark-file'. + (if files + (dolist (file files) (diredp-bookmark prefix file 'NO-MSG-P)) + (dired-map-over-marks-check + (lexical-let ((pref prefix)) #'(lambda () (diredp-bookmark pref nil 'NO-MSG-P))) + arg 'bookmark (diredp-fewer-than-2-files-p arg))) + (bookmark-save) + (unless bfile-exists-p (revert-buffer))) + (unless (bmkp-same-file-p old-bmkp-current-bookmark-file bmkp-current-bookmark-file) + (bmkp-switch-bookmark-file old-bmkp-current-bookmark-file 'NO-MSG)))) + (when bfile-bookmarkp (bmkp-set-bookmark-file-bookmark bookmark-file)))) + +(defun diredp-read-bookmark-file-args () + "Read args for `diredp-do-bookmark-in-bookmark-file' and similar." + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (let* ((insert-default-directory t) + (bmk-file (expand-file-name + (read-file-name + "Use bookmark file (default is in CURRENT dir): " nil + (if (or (> emacs-major-version 23) + (and (= emacs-major-version 23) (> emacs-minor-version 1))) + (list ".emacs.bmk" bookmark-default-file) + ".emacs.bmk"))))) + bmk-file) + (and diredp-prompt-for-bookmark-prefix-flag (read-string "Prefix for autofile bookmark names: ")) + current-prefix-arg)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Allows for consp `dired-directory' too. +;; +(defun dired-buffers-for-dir (dir &optional file) + "Return a list of buffers that Dired DIR (top level or in-situ subdir). +If FILE is non-nil, include only those whose wildcard pattern (if any) +matches FILE. +The list is in reverse order of buffer creation, most recent last. +As a side effect, killed Dired buffers for DIR are removed from +`dired-buffers'." + (setq dir (file-name-as-directory dir)) + (let (result buf) + (dolist (elt dired-buffers) + (setq buf (cdr elt)) + (cond ((null (buffer-name buf)) ; Buffer is killed - clean up. + (setq dired-buffers (delq elt dired-buffers))) + ((dired-in-this-tree dir (car elt)) + (with-current-buffer buf + (and (assoc dir dired-subdir-alist) + (or (null file) + (if (stringp dired-directory) + ;; Allow for consp `dired-directory' too. + (let ((wildcards (file-name-nondirectory (if (consp dired-directory) + (car dired-directory) + dired-directory)))) + (or (zerop (length wildcards)) + (diredp-string-match-p (dired-glob-regexp wildcards) file))) + (member (expand-file-name file dir) (cdr dired-directory)))) + (setq result (cons buf result))))))) + result)) + + +;; If you use library `files+.el', you need not use these commands +;; explicitly, because that library redefines `find-file-read-args' to +;; do the same thing, in Dired mode. These are provided here in case +;; you want to bind them directly - for example, in case your code +;; does not use `find-file-read-args'. +;; +;;;###autoload +(defun diredp-find-a-file (filename &optional wildcards) ; Not bound + "`find-file', but use file on current line as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-other-frame (filename &optional wildcards) ; Not bound + "`find-file-other-frame', but use file under cursor as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file-other-frame filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-other-window (filename &optional wildcards) ; Not bound + "`find-file-other-window', but use file under cursor as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file-other-window filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-read-args (prompt mustmatch) ; Not bound + (list (lexical-let ((find-file-default (abbreviate-file-name (dired-get-file-for-visit)))) + (minibuffer-with-setup-hook (lambda () + (setq minibuffer-default find-file-default)) + (read-file-name prompt nil default-directory mustmatch))) + t)) + +;;;###autoload +(defun diredp-find-file-reuse-dir-buffer () ; Not bound + "Like `dired-find-file', but reuse Dired buffers. +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame)." + (interactive) + (set-buffer-modified-p nil) + (let ((file (dired-get-file-for-visit))) + (diredp--reuse-dir-buffer-helper file))) + +;;;###autoload +(defun diredp-mouse-find-file-reuse-dir-buffer (event &optional find-file-func find-dir-func) ; Not bound + "Like `dired-mouse-find-file', but reuse Dired buffers. +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame). + +Non-nil optional args FIND-FILE-FUNC and FIND-DIR-FUNC specify +functions to visit the file and directory, respectively. +Defaults: `find-file' and `dired', respectively." + (interactive "e") + (let (window pos file) + (save-excursion + (setq window (posn-window (event-end event)) + pos (posn-point (event-end event))) + (unless (windowp window) (error "No file chosen")) + (set-buffer (window-buffer window)) + (goto-char pos) + (setq file (dired-get-file-for-visit))) + (select-window window) + (diredp--reuse-dir-buffer-helper file find-file-func find-dir-func))) + +(defun diredp--reuse-dir-buffer-helper (file &optional find-file-func find-dir-func other-window) + "Helper for commands `diredp-*-reuse-dir-buffer' commands. +Non-nil optional args FIND-FILE-FUNC and FIND-DIR-FUNC specify +functions to visit the file and directory, respectively. +Defaults: `find-file' and `dired', respectively. + +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame)." + (setq find-file-func (or find-file-func (if other-window #'find-file-other-window #'find-file)) + find-dir-func (or find-dir-func (if other-window #'dired-other-window #'dired))) + (let (;; This binding prevents problems with preserving point in windows displaying Dired buffers, because + ;; reverting a Dired buffer empties it, which changes the places where the markers used by + ;; `switch-to-buffer-preserve-window-point' point. + (switch-to-buffer-preserve-window-point (and (boundp 'switch-to-buffer-preserve-window-point) ; Emacs 24+ + (or (not (boundp 'dired-auto-revert-buffer)) + (not dired-auto-revert-buffer)) + switch-to-buffer-preserve-window-point)) + (find-file-run-dired t) + (wins ()) + (alt-find-file-func (if other-window + #'find-alternate-file-other-window + #'find-alternate-file)) + dir-bufs) + (if (or (not (file-directory-p file)) ; New is a not a directory + (dired-buffers-for-dir file) ; or there is a Dired buffer for it, even as a subdir. + (and (setq dir-bufs (dired-buffers-for-dir default-directory)) ; Dired bufs for current (old). + (progn + (dolist (buf dir-bufs) + (setq wins (append wins (get-buffer-window-list buf 'NOMINI 0)))) + (setq wins (delq nil wins)) + (cdr wins)))) ; More than one window showing current Dired buffer. + (if (file-directory-p file) + (or (and (cdr dired-subdir-alist) (dired-goto-subdir file)) ; New is a subdir inserted in current + (funcall find-dir-func file)) + (funcall find-file-func (file-name-sans-versions file t))) + (funcall alt-find-file-func (file-name-sans-versions file t))))) + +;;;###autoload +(defalias 'toggle-diredp-find-file-reuse-dir 'diredp-toggle-find-file-reuse-dir) +;;;###autoload +(defun diredp-toggle-find-file-reuse-dir (force-p) ; Bound to `C-M-R' (aka `C-M-S-r') + "Toggle whether Dired `find-file' commands reuse directories. +This applies also to `dired-w32-browser' commands and +`diredp-up-directory'. + +A prefix arg specifies directly whether or not to reuse. + If its numeric value is non-negative then reuse; else do not reuse. + +To set the behavior as a preference (default behavior), put this in +your ~/.emacs, where VALUE is 1 to reuse or -1 to not reuse: + + (diredp-toggle-find-file-reuse-dir VALUE) + +Note: This affects only these commands: + + `dired-find-file' + `dired-mouse-find-file' + +It does not affect the corresponding `-other-window' commands. Note +too that, by default, mouse clicks to open files or directories open +in another window: command `diredp-mouse-find-file-other-window', not +`dired-mouse-find-file'. If you want a mouse click to reuse a +directory then bind `mouse-2' to `dired-mouse-find-file' instead." + (interactive "P") + (if force-p ; Force. + (if (natnump (prefix-numeric-value force-p)) + (diredp-make-find-file-keys-reuse-dirs) + (diredp-make-find-file-keys-not-reuse-dirs)) + (if (where-is-internal 'dired-find-file dired-mode-map 'ascii) + (diredp-make-find-file-keys-reuse-dirs) + (diredp-make-find-file-keys-not-reuse-dirs)))) + +(defun diredp-make-find-file-keys-reuse-dirs () + "Make find-file keys reuse Dired buffers." + (substitute-key-definition 'diredp-up-directory 'diredp-up-directory-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-find-file 'diredp-find-file-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-mouse-find-file 'diredp-mouse-find-file-reuse-dir-buffer dired-mode-map) + ;; These commands are defined in `w32-browser.el' (for use with MS Windows). + (substitute-key-definition 'dired-w32-browser 'dired-w32-browser-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-mouse-w32-browser 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + (message "Reusing Dired buffers is now ON")) + +(defun diredp-make-find-file-keys-not-reuse-dirs () + "Make find-file keys not reuse Dired buffers (i.e. act normally)." + (substitute-key-definition 'diredp-up-directory-reuse-dir-buffer 'diredp-up-directory dired-mode-map) + (substitute-key-definition 'diredp-find-file-reuse-dir-buffer 'dired-find-file dired-mode-map) + (substitute-key-definition 'diredp-mouse-find-file-reuse-dir-buffer 'dired-mouse-find-file dired-mode-map) + ;; These commands are defined in `w32-browser.el' (for use with MS Windows). + (substitute-key-definition 'dired-w32-browser-reuse-dir-buffer 'dired-w32-browser dired-mode-map) + (substitute-key-definition 'dired-mouse-w32-browser-reuse-dir-buffer 'dired-mouse-w32-browser dired-mode-map) + (message "Reusing Dired buffers is now OFF")) + +;;;###autoload +(defun diredp-omit-marked () ; Not bound + "Omit lines of marked files. Return the number of lines omitted." + (interactive) + (let ((old-modified-p (buffer-modified-p)) + count) + (when (interactive-p) (message "Omitting marked lines...")) + (setq count (dired-do-kill-lines nil "Omitted %d line%s.")) + (set-buffer-modified-p old-modified-p) ; So no `%*' appear in mode-line. + count)) + +;;;###autoload +(defun diredp-omit-unmarked () ; Not bound + "Omit lines of unmarked files. Return the number of lines omitted." + (interactive) + (let ((old-modified-p (buffer-modified-p)) + count) + (dired-toggle-marks) + (message "Omitting unmarked lines...") + (setq count (diredp-omit-marked)) + (dired-toggle-marks) ; Marks all except `.', `..' + (set-buffer-modified-p old-modified-p) ; So no `%*' appear in mode-line. + count)) + +;;;###autoload +(defun diredp-ediff (file2) ; Bound to `=' + "Compare file at cursor with file FILE2 using `ediff'. +FILE2 defaults to the file at the cursor as well. If you enter just a +directory name for FILE2, then the file at the cursor is compared with +a file of the same name in that directory. FILE2 is the second file +given to `ediff'; the file at the cursor is the first. + +Try to guess a useful default value for FILE2, as follows: + +* If the mark is active, use the file at mark. +* Else if the file at cursor is a autosave file or a backup file, use + the corresponding base file. +* Else if there is any backup file for the file at point, use the + newest backup file for it. +* Else use the file at point." + (interactive (progn (require 'ediff) + (list (ediff-read-file-name ; In `ediff-util.el'. + (format "Compare %s with" (dired-get-filename t)) + (dired-current-directory) + (let* ((file (dired-get-filename)) + (file-sans-dir (file-name-nondirectory file)) + (file-dir (file-name-directory file)) + (file-at-mark (and transient-mark-mode + mark-active + (save-excursion (goto-char (mark t)) + (dired-get-filename t t)))) + (last-backup (file-newest-backup file))) + (cond + (file-at-mark) + ((auto-save-file-name-p file-sans-dir) + (expand-file-name (substring file-sans-dir 1 -1) file-dir)) + ((backup-file-name-p file-sans-dir) + (expand-file-name (file-name-sans-versions file-sans-dir) file-dir)) + (last-backup) + (t file))))))) + (ediff-files (dired-get-filename) file2)) ; In `ediff.el'. + +(defun diredp-fewer-than-N-files-p (arg n) + "Return non-nil iff fewer than N files are to be treated by dired. +More precisely, return non-nil iff ARG is nil and fewer than N +files are marked, or the absolute value of ARG is less than N." + (if arg + (and (integerp arg) (< (abs arg) n)) ; Next or previous file (or none). + (not (save-excursion ; Fewer than two marked files. + (goto-char (point-min)) + (re-search-forward (dired-marker-regexp) nil t n))))) + +(defun diredp-fewer-than-2-files-p (arg) + "Return non-nil iff fewer than two files are to be treated by dired. +More precisely, return non-nil iff ARG is nil and fewer than two +files are marked, or ARG is -1, 0 or 1." + (diredp-fewer-than-N-files-p arg 2)) + +(defun diredp-fewer-than-echo-limit-files-p (arg) + "Return non-nil iff < `diredp-do-report-echo-limit' files marked. +More precisely, return non-nil iff ARG is nil and fewer than two +files are marked, or ARG is -1, 0 or 1." + (diredp-fewer-than-N-files-p arg diredp-do-report-echo-limit)) + +;;;###autoload +(defun diredp-do-apply-function (function &optional arg) ; Bound to `@' + "Apply FUNCTION to the marked files. +You are prompted for the FUNCTION. + +With a plain prefix ARG (`C-u'), visit each file and invoke FUNCTION + with no arguments. +Otherwise, apply FUNCTION to each file name. + +Any prefix arg other than single `C-u' behaves according to the ARG +argument of `dired-get-marked-files'. In particular, `C-u C-u' +operates on all files in the Dired buffer. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily." + (interactive (progn (diredp-ensure-mode) + (list (read (completing-read "Function: " obarray 'functionp nil nil + (and (boundp 'function-name-history) + 'function-name-history))) + current-prefix-arg))) + (let ((use-no-args-p (and (consp arg) (< (car arg) 16)))) + (when use-no-args-p (setq arg ())) + (save-selected-window + (diredp-map-over-marks-and-report + (if use-no-args-p #'diredp-invoke-function-no-args #'diredp-apply-function-to-file-name) + arg + 'apply\ function (diredp-fewer-than-2-files-p arg) + function + (diredp-fewer-than-echo-limit-files-p arg))))) + +(defun diredp-invoke-function-no-args (fun &optional echop) + "Visit file of this line at its beginning, then invoke function FUN. +No arguments are passed to FUN. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (funcall fun)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + +(defun diredp-apply-function-to-file-name (fun &optional echop) + "Apply function FUN to (absolute) file name on this line. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let ((file (dired-get-filename)) + (failure nil) + result) + (condition-case err + (setq result (funcall fun file)) + (error (setq failure err))) + (diredp-report-file-result file result failure echop))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+'s version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-compress (&optional arg) ; Bound to `Z' + "Compress or uncompress marked (or next prefix argument) files. +A prefix argument ARG specifies files to use instead of marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive "P") + (dired-map-over-marks-check #'dired-compress arg 'compress (diredp-fewer-than-2-files-p arg))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+'s version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-byte-compile (&optional arg) ; Bound to `B' + "Byte compile marked Emacs Lisp files. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg))) + (dired-map-over-marks-check #'dired-byte-compile arg 'byte-compile + (diredp-fewer-than-2-files-p arg))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+' version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-load (&optional arg) ; Bound to `L' + "Load the marked Emacs Lisp files. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg))) + (dired-map-over-marks-check #'dired-load arg 'load (diredp-fewer-than-2-files-p arg))) + + +(when (fboundp 'multi-isearch-files) + + ;; REPLACE ORIGINAL in `dired.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-isearch (&optional arg interactivep) + "Search for a string through all marked files using Isearch. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg t))) + (multi-isearch-files (dired-get-marked-files nil arg 'dired-nondirectory-p nil interactivep))) + + + ;; REPLACE ORIGINAL in `dired.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-isearch-regexp (&optional arg interactivep) + "Search for a regexp through all marked files using Isearch. +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg t))) + (multi-isearch-files-regexp (dired-get-marked-files nil arg 'dired-nondirectory-p nil interactivep))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el': +;; +;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. +;; 2. Added optional arg INTERACTIVEP. +;; 3. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-search (regexp &optional arg interactivep) + "Search through all marked files for a match for REGEXP. +Stops when a match is found. +To continue searching for next match, use command \\[tags-loop-continue]. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (diredp-read-regexp "Search marked files (regexp): ") + arg + t))) + (tags-search regexp `(dired-get-marked-files nil ',arg 'dired-nondirectory-p nil ,interactivep))) + + +;; REPLACE ORIGINAL in `dired-aux.el': +;; +;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. +;; 2. Added optional arg INTERACTIVEP. +;; 3. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-query-replace-regexp (from to &optional arg interactivep) + "Do `query-replace-regexp' of FROM with TO, on all marked files. +NOTE: A prefix arg for this command acts differently than for other +commands, so that you can use it to request word-delimited matches. + +With a prefix argument: + * An odd number of plain `C-u': act on the marked files, but replace + only word-delimited matches. + * More than one plain `C-u': act on all files, ignoring whether any + are marked. + * Any other prefix arg: Act on the next numeric-prefix files. + +So for example: + * `C-u C-u C-u': act on all files, replacing word-delimited matches. + * `C-u 4': act on the next 4 files. `C-4' means the same thing. + * `C-u': act on the marked files, replacing word-delimited matches. + +When invoked interactively, raise an error if no files are marked. + +If you exit (\\[keyboard-quit], RET or q), you can resume the query replace +with the command \\[tags-loop-continue]." + (interactive (let ((common (query-replace-read-args "Query replace regexp in marked files" t t))) + (list (nth 0 common) + (nth 1 common) + current-prefix-arg + t))) + (let* ((argnum (and (consp arg) (prefix-numeric-value arg))) + (delimited (and argnum (eq (logand (truncate (log argnum 4)) 1) 1))) ; Odd number of plain `C-u'. + (all (and argnum (> argnum 4))) ; At least 3 plain `C-u'. + (dgmf-arg (dired-get-marked-files nil + (if (and arg (atom arg)) (abs arg) (and all '(16))) + 'dired-nondirectory-p + nil + interactivep))) + (dolist (file dgmf-arg) + (let ((buffer (get-file-buffer file))) + (when (and buffer (with-current-buffer buffer buffer-read-only)) + (error "File `%s' is visited read-only" file)))) + (tags-query-replace from to delimited `',dgmf-arg))) + + +(when (fboundp 'xref-collect-matches) ; Emacs 25+ + + + ;; REPLACE ORIGINAL in `dired-aux.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-find-regexp (regexp &optional arg interactivep) + "Find all matches for REGEXP in all marked files. +For any marked directory, all of its files are searched recursively. +However, files matching `grep-find-ignored-files' and subdirectories +matching `grep-find-ignored-directories' are skipped in the marked +directories. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked. + +REGEXP should use constructs supported by your local `grep' command." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (diredp-read-regexp "Search marked files (regexp): ") + arg + t))) + (require 'grep) + (defvar grep-find-ignored-files) + (defvar grep-find-ignored-directories) + (let* ((files (dired-get-marked-files nil arg nil nil interactivep)) + (ignores (nconc (mapcar (lambda (s) (concat s "/")) grep-find-ignored-directories) + grep-find-ignored-files)) + (xrefs (mapcan (lambda (file) + (xref-collect-matches + regexp "*" file (and (file-directory-p file) ignores))) + files))) + (if xrefs + (xref--show-xrefs xrefs nil t) + (when interactivep (diredp-user-error "No matches for: %s" regexp))))) + + + ;; REPLACE ORIGINAL in `dired-aux.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; +;;;###autoload + (defun dired-do-find-regexp-and-replace (from to &optional arg interactivep) + "Replace matches of FROM with TO, in all marked files. +For any marked directory, matches in all of its files are replaced, +recursively. However, files matching `grep-find-ignored-files' +and subdirectories matching `grep-find-ignored-directories' are skipped +in the marked directories. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked. + +REGEXP should use constructs supported by your local `grep' command." + (interactive (let ((common (query-replace-read-args "Query replace regexp in marked files" t t)) + (arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (nth 0 common) + (nth 1 common) + arg + t))) + (with-current-buffer (dired-do-find-regexp from arg interactivep) + (xref-query-replace-in-results from to))) + + ) + +;;;###autoload +(defun diredp-do-grep (command-args) ; Bound to `C-M-G' + "Run `grep' on marked (or next prefix arg) files. +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (unless (if (< emacs-major-version 22) + grep-command + (and grep-command (or (not grep-use-null-device) (eq grep-use-null-device t)))) + (grep-compute-defaults)) + (list (diredp-do-grep-1)))) + (grep command-args)) + +;; Optional arg FILES is no longer used. It was used in `diredp-do-grep' before the +;; new `dired-get-marked-files'. +(defun diredp-do-grep-1 (&optional files) + "Helper function for `diredp-do-grep'. +Non-nil optional arg FILES are the files to grep, overriding the files +choice described for `diredp-do-grep'." + (let ((default (and (fboundp 'grep-default-command) + (if (fboundp 'grepp-default-regexp-fn) ; In `grep+.el'. + (grep-default-command (funcall (grepp-default-regexp-fn))) + (grep-default-command))))) + (read-from-minibuffer + "grep <pattern> <files> : " + (let ((up-to-files (concat grep-command " "))) + (cons (concat up-to-files + (mapconcat #'identity + (or files (mapcar 'shell-quote-argument + (dired-get-marked-files nil current-prefix-arg))) + " ")) + (- (length up-to-files) 2))) + nil nil 'grep-history default))) + +(when (memq system-type '(windows-nt ms-dos)) + (define-derived-mode diredp-w32-drives-mode fundamental-mode "Drives" + "Mode for Dired buffer listing MS Windows drives (local or remote)." + (setq buffer-read-only t))) + +;; The next two commands were originally taken from Emacs Wiki, page WThirtyTwoBrowseNetDrives: +;; https://www.emacswiki.org/emacs/WThirtyTwoBrowseNetDrives. They are referred to there as +;; commands `show-net-connections' and `netdir'. I am hoping that the contributor (anonymous) +;; does not mind my adapting them and including them in `Dired+'. + +(when (memq system-type '(windows-nt ms-dos)) + (defun diredp-w32-list-mapped-drives () ; Not bound + "List network connection information for shared MS Windows resources. +This just invokes the Windows `NET USE' command." + (interactive) + (shell-command "net use") + (display-buffer "*Shell Command Output*"))) + +(when (memq system-type '(windows-nt ms-dos)) + (defun diredp-w32-drives (&optional other-window-p) ; Bound to `:/' + "Visit a list of MS Windows drives for use by Dired. +With a prefix argument use another window for the list. +In the list, use `mouse-2' or `RET' to open Dired for a given drive. + +The drives listed are the remote drives currently available, as +determined by the Windows command `NET USE', plus the local drives +specified by option `diredp-w32-local-drives', which you can +customize. + +Note: When you are in Dired at the root of a drive (e.g. directory + `C:/'), command `diredp-up-directory' invokes this command. + So you can use `\\[diredp-up-directory]' to go up to the list of drives." + (interactive "P") + (require 'widget) + (let ((drive (copy-sequence diredp-w32-local-drives)) + (inhibit-read-only t)) + (with-temp-buffer + (insert (shell-command-to-string "net use")) + (goto-char (point-min)) + (while (re-search-forward "[A-Z]: +\\\\\\\\[^ ]+" nil t nil) + (setq drive (cons (split-string (match-string 0)) drive)))) + (if other-window-p + (pop-to-buffer "*Windows Drives*") + (if (fboundp 'pop-to-buffer-same-window) + (pop-to-buffer-same-window "*Windows Drives*") + (switch-to-buffer "*Windows Drives*"))) + (erase-buffer) + (widget-minor-mode 1) + (dolist (drv (sort drive (lambda (a b) (string-lessp (car a) (car b))))) + (lexical-let ((drv drv)) + (widget-create 'push-button + :notify (lambda (widget &rest ignore) (dired (car drv))) + (concat (car drv) " " (cadr drv)))) + (widget-insert "\n")) + (goto-char (point-min)) + (diredp-w32-drives-mode)))) + +;; $$$$$$ NO LONGER USED. Was used in `diredp-do-grep(-1)' before new `dired-get-marked-files'. +(defun diredp-all-files () + "List of all files shown in current Dired buffer. +Directories are not included." + (let ((pos (make-marker)) + (files ()) + file) + (save-excursion + (goto-char (point-min)) (beginning-of-line) + (while (not (eobp)) + (beginning-of-line) + (while (and (not (eobp)) (dired-between-files)) (forward-line 1)) + (save-excursion (forward-line 1) (move-marker pos (1+ (point)))) + (setq file (dired-get-filename nil t)) ; Non-nil second arg means "also . and ..". + (when file ; Remove directory portion if in same directory. + (setq file (dired-get-filename (dired-in-this-tree file default-directory) t))) + (unless (or (not file) (file-directory-p file)) (push file files)) + (goto-char pos)) + (move-marker pos nil)) + (setq files (sort files (if (and (featurep 'ls-lisp) + (not (symbol-value 'ls-lisp-use-insert-directory-program))) + 'ls-lisp-string-lessp + (if case-fold-search + (lambda (s1 s2) (string-lessp (upcase s1) (upcase s2))) + 'string-lessp)))))) + +(when (fboundp 'read-char-choice) ; Emacs 24+ + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; `l' lists the files involved and prompts again. + ;; + (defun dired-query (sym prompt &rest args) + "Format PROMPT with ARGS, query user, and store the result in SYM. +The return value is either nil or t. + +The user can type: + `y' or `SPC' to accept once + `n' or `DEL' to skip once + `!' to accept this and subsequent queries + `l' list the files, showing details per `diredp-list-file-attributes' + `q' or `ESC' to decline this and subsequent queries + +If SYM is already bound to a non-nil value, this function may return +automatically without querying the user. If SYM is `!', return t; if +SYM is `q' or ESC, return nil." + (let* ((char (symbol-value sym)) + (char-choices '(?y ?\ ?n ?\177 ?! ?l ?q ?\e)) ; Use ?\ , not ?\s, for Emacs 20 byte-compiler. + (list-buf (generate-new-buffer-name "*Files*")) + (list-was-shown nil)) + (unwind-protect + (cond ((eq char ?!) t) ; Accept, and don't ask again. + ((memq char '(?q ?\e)) nil) ; Skip, and don't ask again. + (t ; No previous answer - ask now + (setq prompt (concat (apply (if (fboundp 'format-message) #'format-message #'format) + prompt + args) + (if help-form + (format " [Type ynlq! or %s] " (key-description (vector help-char))) + " [Type y, n, l, q or !] "))) + (set sym (setq char (read-char-choice prompt char-choices))) + (when (eq char ?l) ; List files and prompt again. + (diredp-list-files args nil nil nil diredp-list-file-attributes) + (set sym (setq char (read-char-choice prompt char-choices)))) + (and (memq char '(?y ?\ ?!)) t))) ; Use ?\ , not ?\s, for Emacs 20. + (when (get-buffer list-buf) + (save-window-excursion + (pop-to-buffer list-buf) + (condition-case nil ; Ignore error if user already deleted. + (if (one-window-p) (delete-frame) (delete-window)) + (error nil)) + (if list-was-shown (bury-buffer list-buf) (kill-buffer list-buf))))))) + + ) + +(unless (fboundp 'read-char-choice) ; Emacs 20-23 (modified the Emacs 23 version). Needs `dired-query-alist'. + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. `l' lists the files involved and prompts again. + ;; 2. Compatible with older Emacs versions (before Emacs 24): can use `dired-query-alist'. + ;; + (defun dired-query (qs-var qs-prompt &rest qs-args) + "Query user and return nil or t. +The user can type: + `y' or `SPC' to accept once + `n' or `DEL' to skip once + `!' to accept this and subsequent queries + `l' list the files, showing details per `diredp-list-file-attributes' + `q' or `ESC' to decline this and subsequent queries + +Store answer in symbol VAR (which must initially be bound to nil). +Format PROMPT with ARGS. +Binding variable `help-form' will help the user who types the help key." + (let* ((char (symbol-value qs-var)) + (dired-query-alist (cons '(?l . l) dired-query-alist)) + (action (cdr (assoc char dired-query-alist)))) + (cond ((eq 'yes action) t) ; Accept, and don't ask again. + ((eq 'no action) nil) ; Skip, and don't ask again. + (t ; No lasting effects from last time we asked - ask now. + (let ((cursor-in-echo-area t) + (executing-kbd-macro executing-kbd-macro) + (qprompt (concat qs-prompt + (if help-form + (format " [Type ynl!q or %s] " + (key-description (char-to-string help-char))) + " [Type y, n, l, q or !] "))) + done result elt) + (while (not done) + (apply #'message qprompt qs-args) + (setq char (set qs-var (read-event))) + (when (eq char ?l) ; List files and prompt again. + (diredp-list-files qs-args nil nil nil diredp-list-file-attributes) + (apply #'message qprompt qs-args) + (setq char (set qs-var (read-event)))) + (if (numberp char) + (cond ((and executing-kbd-macro (= char -1)) + ;; `read-event' returns -1 if we are in a keyboard macro and there are no more + ;; events in the macro. Try to get an event interactively. + (setq executing-kbd-macro nil)) + ((eq (key-binding (vector char)) 'keyboard-quit) (keyboard-quit)) + (t (setq done (setq elt (assoc char dired-query-alist))))))) + ;; Display the question with the answer. + (message "%s" (concat (apply #'format qprompt qs-args) (char-to-string char))) + (memq (cdr elt) '(t y yes))))))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Use `diredp-this-subdir' instead of `dired-get-filename'. +;; 2. If on a subdir listing header line or a non-dir file in a subdir listing, go to +;; the line for the subdirectory in the parent directory listing. +;; 3. Fit one-window frame after inserting subdir. +;; +;;;###autoload +(defun dired-maybe-insert-subdir (dirname &optional switches no-error-if-not-dir-p) + ; Bound to `i' + "Move to Dired subdirectory line or subdirectory listing. +This bounces you back and forth between a subdirectory line and its +inserted listing header line. Using it on a non-directory line in a +subdirectory listing acts the same as using it on the subdirectory +header line. + +* If on a subdirectory line, then go to the subdirectory's listing, + creating it if not yet present. + +* If on a subdirectory listing header line or a non-directory file in + a subdirectory listing, then go to the line for the subdirectory in + the parent directory listing. + +* If on a non-directory file in the top Dired directory listing, do + nothing. + +Subdirectories are listed in the same position as for `ls -lR' output. + +With a prefix arg, you can edit the `ls' switches used for this +listing. Add `R' to the switches to expand the directory tree under a +subdirectory. + +Dired remembers the switches you specify with a prefix arg, so +reverting the buffer does not reset them. However, you might +sometimes need to reset some subdirectory switches after a +`dired-undo'. You can reset all subdirectory switches to the +default value using \\<dired-mode-map>\\[dired-reset-subdir-switches]. See \ +Info node +`(emacs)Subdir switches' for more details." + (interactive (list (diredp-this-subdir) + (and current-prefix-arg + (read-string "Switches for listing: " + (or (and (boundp 'dired-subdir-switches) dired-subdir-switches) + dired-actual-switches))))) + (let ((opoint (point)) + (filename dirname)) + (cond ((consp filename) ; Subdir header line or non-directory file. + (setq filename (car filename)) + (if (assoc filename dired-subdir-alist) + (dired-goto-file filename) ; Subdir header line. + (dired-insert-subdir (substring (file-name-directory filename) 0 -1)))) + (t + ;; We don't need a marker for opoint as the subdir is always + ;; inserted *after* opoint. + (setq dirname (file-name-as-directory dirname)) + (or (and (not switches) (dired-goto-subdir dirname)) + (dired-insert-subdir dirname switches no-error-if-not-dir-p)) + ;; Push mark so that it's easy to go back. Do this after the + ;; insertion message so that the user sees the `Mark set' message. + (push-mark opoint) + (when (and (get-buffer-window (current-buffer)) ; Fit one-window frame. + (fboundp 'fit-frame-if-one-window)) ; In `autofit-frame.el'. + (fit-frame-if-one-window)))))) + +(defun diredp-this-subdir () + "This line's filename, if directory, or `dired-current-directory' list. +If on a directory line, then return the directory name. +Else return a singleton list of a directory name, which is as follows: + If on a subdirectory header line (either of the two lines), then use + that subdirectory name. Else use the parent directory name." + (or (let ((file (dired-get-filename nil t))) + (and file + (file-directory-p file) + (not (member (file-relative-name file (file-name-directory (directory-file-name file))) + '("." ".." "./" "../"))) + file)) + (list (dired-current-directory)))) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; 1. Added optional arg FROM, which is also listed by `l' when prompted. +;; 2. Added missing doc string. +;; +(defun dired-handle-overwrite (to &optional from) + "Save old version of file TO that is to be overwritten. +`dired-overwrite-confirmed' and `overwrite-backup-query' are fluid vars +from `dired-create-files'. + +Optional arg FROM is a file being copied or renamed to TO. It is used +only when a user hits `l' to list files when asked whether to +overwrite." + (let (backup) + (when (and dired-backup-overwrite + dired-overwrite-confirmed + (setq backup (car (find-backup-file-name to))) + (or (eq 'always dired-backup-overwrite) + (dired-query 'overwrite-backup-query "Make backup for existing file `%s'? " to from))) + (rename-file to backup 0) ; Confirm overwrite of old backup. + (dired-relist-entry backup)))) + + +(when (fboundp 'dired-copy-file-recursive) ; Emacs 22+ + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. Pass also FROM to `dired-handle-overwrite', so `l' lists it too. + ;; 2. Added missing doc string. + ;; + (defun dired-copy-file (from to ok-if-already-exists) + "Copy file FROM to location TO. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `copy-file' or + `make-symbolic-link'. +Preserves the last-modified date when copying, unless +`dired-copy-preserve-time' is nil." + (dired-handle-overwrite to from) + (dired-copy-file-recursive from to ok-if-already-exists dired-copy-preserve-time t dired-recursive-copies)) + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. Pass also FROM to `dired-handle-overwrite', so `l' lists it too. + ;; 2. Added missing doc string. + ;; + (defun dired-copy-file-recursive (from to ok-if-already-exists &optional keep-time top recursive) + "Copy file FROM to location TO, handling directories in FROM recursively. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `copy-file' or + `make-symbolic-link'. +Non-nil optional arg KEEP-TIME is passed to `copy-file' or + `copy-directory'. +Non-nil optional arg TOP means do not bother with `dired-handle-overwrite'. +Non-nil optional arg RECURSIVE means recurse on any directories in + FROM, after confirmation if RECURSIVE is not `always'." + (when (and (eq t (car (file-attributes from))) (file-in-directory-p to from)) + (error "Cannot copy `%s' into its subdirectory `%s'" from to)) + (let ((attrs (file-attributes from))) + (if (and recursive + (eq t (car attrs)) + (or (eq recursive 'always) (yes-or-no-p (format "Recursive copies of %s? " from)))) + (copy-directory from to keep-time) + (or top (dired-handle-overwrite to from)) + (condition-case err + (if (stringp (car attrs)) ; It is a symlink + (make-symbolic-link (car attrs) to ok-if-already-exists) + (copy-file from to ok-if-already-exists keep-time)) + (file-date-error + (push (dired-make-relative from) dired-create-files-failures) + (dired-log "Can't set date on %s:\n%s\n" from err)))))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; 1. Pass also FILE to `dired-handle-overwrite', so `l' lists it too. +;; 2. Added missing doc string. +;; +(defun dired-rename-file (file newname ok-if-already-exists) + "Rename FILE to NEWNAME. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `rename-file'." + (dired-handle-overwrite newname file) + (rename-file file newname ok-if-already-exists) ; Error is caught in `-create-files'. + ;; Silently rename the visited file of any buffer visiting this file. + (and (get-file-buffer file) (with-current-buffer (get-file-buffer file) (set-visited-file-name newname nil t))) + (dired-remove-file file) + ;; See if it's an inserted subdir, and rename that, too. + (dired-rename-subdir file newname)) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; Pass also FILE to `dired-handle-overwrite', so `l' lists it too. +;; +(defun dired-hardlink (file newname &optional ok-if-already-exists) + "Give FILE additional name NEWNAME. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `add-name-to-file'." + (dired-handle-overwrite newname file) + (add-name-to-file file newname ok-if-already-exists) ; Error is caught in -create-files'. + (dired-relist-file file)) ; Update the link count. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; No-op: does nothing now. +;; +(defun dired-insert-subdir-validate (dirname &optional switches)) + + +;;; $$$$$$$$ +;;; ;; REPLACE ORIGINAL in `dired-aux.el'. +;;; ;; +;;; ;; 1. Do not require that DIRNAME be in the current directory tree (no error if not). +;;; ;; 2. Use `dolist' instead of `mapcar'. +;;; ;; +;;; (defun dired-insert-subdir-validate (dirname &optional switches) +;;; "Raise an error if it is invalid to insert DIRNAME with SWITCHES." +;;; ;;; (or (dired-in-this-tree dirname (expand-file-name default-directory)) ; REMOVED +;;; ;;; (error "%s: not in this directory tree" dirname)) +;;; (let ((real-switches (or switches (and (boundp 'dired-subdir-switches) ; Emacs 22+ +;;; dired-subdir-switches)))) +;;; (when real-switches +;;; (let (case-fold-search) +;;; (dolist (switchs '("F" "b")) ; Switches that matter for `dired-get-filename'. +;;; (unless (eq (null (diredp-string-match-p switchs real-switches)) +;;; (null (diredp-string-match-p switchs dired-actual-switches))) +;;; (error "Can't have dirs with and without `-%s' switches together" switchs))))))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; If NEW-DIR is not a descendant of a directory in the buffer, put it at eob. +;; +(defun dired-insert-subdir-newpos (new-dir) + "Move to the proper position for inserting NEW-DIR, and return it. +Respect the order within each directory tree. But if NEW-DIR is not a +descendant of any directory in the buffer, then put it at the end." + (let ((alist dired-subdir-alist) + elt dir new-pos) + (while alist + (setq elt (car alist) + alist (cdr alist) + dir (car elt)) + (if (dired-tree-lessp dir new-dir) + (setq new-pos (dired-get-subdir-max elt) ; Position NEW-DIR after DIR. + alist ()) + (setq new-pos (point-max)))) + (goto-char new-pos)) + (unless (eobp) (forward-line -1)) + (insert "\n") + (point)) + + +;; This is like original `dired-hide-subdir' in `dired-aux.el', except: +;; +;; 1. Plain prefix arg means invoke `dired-hide-all'. Added optional arg NEXT. +;; 2. Do not move to the next subdir. +;; 3. Modified to work with also with older Emacs versions. +;; +(defun diredp-hide-subdir-nomove (arg &optional next) + "Hide or unhide the current directory. +Unlike `dired-hide-subdir', this does not advance the cursor to the +next directory header line. + +With a plain prefix arg (`C-u'), invoke `dired-hide-all' to hide or + show everything. +With a numeric prefix arg N, hide this subdirectory and the next N-1 + subdirectories." + (interactive "P") + (dired-hide-check) + (if (consp arg) + (dired-hide-all 'IGNORED) ; Arg needed for older Emacs versions. + (setq arg (prefix-numeric-value arg)) + (let ((modflag (buffer-modified-p))) + (while (>= (setq arg (1- arg)) 0) + (let* ((cur-dir (dired-current-directory)) + (hidden-p (dired-subdir-hidden-p cur-dir)) + (elt (assoc cur-dir dired-subdir-alist)) + (end-pos (1- (dired-get-subdir-max elt))) + buffer-read-only) + (goto-char (dired-get-subdir-min elt)) ; Keep header line visible, hide rest + (skip-chars-forward "^\n\r") + (if hidden-p + (subst-char-in-region (point) end-pos ?\r ?\n) + (subst-char-in-region (point) end-pos ?\n ?\r))) + (when next (dired-next-subdir 1 t))) + (if (fboundp 'restore-buffer-modified-p) + (restore-buffer-modified-p modflag) + (set-buffer-modified-p modflag))))) + +;;; ---------------------- +;;; If we instead renamed `diredp-hide-subdir-nomove' to `dired-hide-subdir' as a replacement, +;;; then we would define things this way: +;;; +;;; +;;; ;; REPLACE ORIGINAL in `dired-aux.el'. +;;; ;; +;;; ;; 1. Plain prefix arg means invoke `dired-hide-all'. Added optional arg NEXT. +;;; ;; +;;; ;; 2. Do not move to the next subdir. +;;; ;; +;;; ;; 3. Modified to work with also with older Emacs versions. +;;; ;; +;;; (defun dired-hide-subdir (arg &optional next) +;;; "Hide or unhide the current directory. +;;; Unlike `diredp-hide-subdir-goto-next', this does not advance the +;;; cursor to the next directory header line. +;;; +;;; With a plain prefix arg (`C-u'), invoke `dired-hide-all' to hide or +;;; show everything. +;;; With a numeric prefix arg N, hide this subdirectory and the next N-1 +;;; subdirectories." +;;; (interactive "P") +;;; (dired-hide-check) +;;; (if (consp arg) +;;; (dired-hide-all 'IGNORED) ; Arg needed for older Emacs versions. +;;; (setq arg (prefix-numeric-value arg)) +;;; (let ((modflag (buffer-modified-p))) +;;; (while (>= (setq arg (1- arg)) 0) +;;; (let* ((cur-dir (dired-current-directory)) +;;; (hidden-p (dired-subdir-hidden-p cur-dir)) +;;; (elt (assoc cur-dir dired-subdir-alist)) +;;; (end-pos (1- (dired-get-subdir-max elt))) +;;; buffer-read-only) +;;; (goto-char (dired-get-subdir-min elt)) ; Keep header line visible, hide rest +;;; (skip-chars-forward "^\n\r") +;;; (if hidden-p +;;; (subst-char-in-region (point) end-pos ?\r ?\n) +;;; (subst-char-in-region (point) end-pos ?\n ?\r))) +;;; (when next (dired-next-subdir 1 t))) +;;; (if (fboundp 'restore-buffer-modified-p) +;;; (restore-buffer-modified-p modflag) +;;; (set-buffer-modified-p modflag))))) +;;; +;;; (defun diredp-hide-subdir-goto-next (arg) +;;; "Hide or unhide current directory and move to next directory header line." +;;; (interactive "P") +;;; (dired-hide-subdir arg 'NEXT)) +;;; ---------------------- + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Fix the `interactive' spec. This is the Emacs 24+ version, provided for earlier versions. +;; +(unless (> emacs-major-version 23) + (defun dired-mark-unmarked-files (regexp msg &optional unflag-p localp) + "Mark unmarked files matching REGEXP, displaying MSG. +REGEXP is matched against the entire file name. When called +interactively, prompt for REGEXP. +With prefix argument, unflag all those files. + +Non-interactively: + Returns t if any work was done, nil otherwise. + Optional fourth argument LOCALP is as in `dired-get-filename'." + (interactive (list (diredp-read-regexp "Mark unmarked files matching regexp (default all): ") + nil + current-prefix-arg + nil)) + (let ((dired-marker-char (if unflag-p ?\ dired-marker-char)) + (unmarkedp (eq (char-after) ?\ ))) + (diredp-mark-if (and (if unflag-p (not unmarkedp) unmarkedp) ; Fixes Emacs bug #27465. + (let ((fn (dired-get-filename localp 'NO-ERROR))) ; Uninteresting + (and fn (diredp-string-match-p regexp fn)))) + msg)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; 1. Call `dired-get-marked-files' with original ARG, to get its multi-`C-u' behavior. +;; 2. Doc string updated to reflect change to `dired-simultaneous-find-file'. +;; 3. Added optional arg INTERACTIVEP. +;; 4. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-find-marked-files (&optional arg interactivep) ; Bound to `F' + "Find marked files, displaying all of them simultaneously. +With no prefix argument: + +* If `pop-up-frames' is nil then split the current window across all + marked files, as evenly as possible. Remaining lines go to the + bottom-most window. The number of files that can be displayed this + way is restricted by the height of the current window and + `window-min-height'. + +* If `pop-up-frames' is non-nil then show each marked file in a + separate frame (not window). + +With a prefix argument: + +* One or more plain `C-u' behaves as for `dired-get-marked-files'. + In particular, `C-u C-u' means ignore any markings and operate on + ALL files and directories (except `.' and `..') in the Dired buffer. + +* A numeric prefix arg >= 0 means just find (visit) the marked files - + do not show them. + +* A numeric prefix arg < 0 means show each marked file in a separate + frame (not window). (This is the same behavior as no prefix arg + with non-nil `pop-up-frames'.) + +Note that a numeric prefix argument acts differently with this command +than it does with other `dired-do-*' commands: it does NOT act on the +next or previous (abs ARG) files, ignoring markings. + +To keep the Dired buffer displayed, split the window (e.g., `C-x 2') +first. To show only the marked files, type `\\[delete-other-windows]' first. + +When invoked interactively, raise an error if no files are marked." + (interactive "P\np") + (dired-simultaneous-find-file + (dired-get-marked-files nil (and (consp arg) arg) nil nil interactivep) + (and arg (prefix-numeric-value arg)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Use separate frames instead of windows if `pop-up-frames' is non-nil, +;; or if prefix arg is negative. +;; +(defun dired-simultaneous-find-file (file-list option) + "Visit all files in list FILE-LIST and display them simultaneously. +With non-nil OPTION >= 0, the files are found (visited) but not shown. + +If `pop-up-frames' is non-nil or if OPTION < 0, use a separate frame +for each file. (See also option `diredp-max-frames'.) + +Otherwise, the current window is split across all files in FILE-LIST, +as evenly as possible. Remaining lines go to the bottom-most window. +The number of files that can be displayed this way is restricted by +the height of the current window and the value of variable +`window-min-height'." + ;; This is not interactive because it is usually too clumsy to specify FILE-LIST interactively unless via dired. + (let (size) + (cond ((and option (natnump option)) + (while file-list (find-file-noselect (car file-list)) (pop file-list))) + ((or pop-up-frames option) + (let ((nb-files (length file-list))) + (when (and (> nb-files diredp-max-frames) + (not (y-or-n-p (format "Really show %d files in separate frames? " nb-files)))) + (error "OK, canceled")) + (while file-list (find-file-other-frame (car file-list)) (pop file-list)))) + (t + (setq size (/ (window-height) (length file-list))) + (when (> window-min-height size) (error "Too many files to show simultaneously")) + (find-file (car file-list)) + (pop file-list) + (while file-list + ;; Vertically split off a window of desired size. Upper window will have SIZE lines. + ;; Select lower (larger) window. We split it again. + (select-window (split-window nil size)) + (find-file (car file-list)) + (pop file-list)))))) + + +;;;;;; REPLACE ORIGINAL in both `dired.el' and `dired-x.el': +;;;;;; +;;;;;; 1. This incorporates the `dired-x.el' change to the `dired.el' +;;;;;; definition. This version works with or without using dired-x. +;;;;;; The `dired-x.el' version respects the var `dired-find-subdir'. +;;;;;; When `dired-find-subdir' is non-nil, this version is the same +;;;;;; as the `dired-x.el' version, except that a bug is corrected: +;;;;;; Whenever the argument to `dired-find-buffer-nocreate' is a cons, +;;;;;; the call to `dired-buffers-for-dir' gave a wrong type error. +;;;;;; This has been avoided by not respecting `dired-find-subdir' +;;;;;; whenever `dired-find-buffer-nocreate' is a cons. +;;;;;; For the case when `dired-find-subdir' is nil, see #2, below. +;;;;;; +;;;;;; 2. Unless `dired-find-subdir' is bound and non-nil: +;;;;;; If both DIRNAME and `dired-directory' are conses, then only +;;;;;; compare their cars (directories), not their explicit file lists +;;;;;; too. If equal, then update `dired-directory's file list to that +;;;;;; of DIRNAME. +;;;;;; +;;;;;; This prevents `dired-internal-noselect' (which is currently +;;;;;; `dired-find-buffer-nocreate's only caller) from creating a new +;;;;;; buffer in this case whenever a different set of files is present +;;;;;; in the cdr of DIRNAME and DIRNAME represents the same buffer as +;;;;;; `dired-directory'. +;;;;;; +;;;;;; If only one of DIRNAME and `dired-directory' is a cons, then +;;;;;; this returns nil. +;;;;;;;###autoload +;;;;(defun dired-find-buffer-nocreate (dirname &optional mode) +;;;; (let ((atomic-dirname-p (atom dirname))) +;;;; (if (and (boundp 'dired-find-subdir) dired-find-subdir atomic-dirname-p) +;;;; ;; This is the `dired-x.el' change: +;;;; (let* ((cur-buf (current-buffer)) +;;;; (buffers (nreverse (dired-buffers-for-dir dirname))) +;;;; (cur-buf-matches (and (memq cur-buf buffers) +;;;; ;; Files list (wildcards) must match, too: +;;;; (equal dired-directory dirname)))) +;;;; (setq buffers (delq cur-buf buffers)) ; Avoid using same buffer--- +;;;; (or (car (sort buffers (function dired-buffer-more-recently-used-p))) +;;;; (and cur-buf-matches cur-buf))) ; ---unless no other possibility. +;;;; ;; Comment from `dired.el': +;;;; ;; This differs from `dired-buffers-for-dir' in that it doesn't consider +;;;; ;; subdirs of `default-directory' and searches for the first match only. +;;;; (let ((blist dired-buffers) ; was (buffer-list) +;;;; found) +;;;; (or mode (setq mode 'dired-mode)) +;;;; (while blist +;;;; (if (null (buffer-name (cdr (car blist)))) +;;;; (setq blist (cdr blist)) +;;;; (save-excursion +;;;; (set-buffer (cdr (car blist))) +;;;; (if (not (and (eq major-mode mode) +;;;; ;; DIRNAME and `dired-directory' have the same dir, +;;;; ;; and if either of them has an explicit file list, +;;;; ;; then both of them do. In that case, update +;;;; ;; `dired-directory's file list from DIRNAME. +;;;; (if atomic-dirname-p +;;;; (and (atom dired-directory) ; Both are atoms. +;;;; (string= (file-truename dirname) +;;;; (file-truename dired-directory))) +;;;; (and (consp dired-directory) ; Both are conses. +;;;; (string= +;;;; (file-truename (car dirname)) +;;;; (file-truename (car dired-directory))) +;;;; ;; Update `dired-directory's file list. +;;;; (setq dired-directory dirname))))) +;;;; (setq blist (cdr blist)) +;;;; (setq found (cdr (car blist))) +;;;; (setq blist nil))))) +;;;; found)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Require confirmation. Fixes Emacs bug #13561. +;; +(defun dired-do-run-mail () + "If `dired-bind-vm' is non-nil, call `dired-vm', else call `dired-rmail'." + (interactive) + (unless (y-or-n-p "Read all marked mail folders? ") (error "OK, canceled")) + (if dired-bind-vm + ;; Read mail folder using vm. + (dired-vm) + ;; Read mail folder using rmail. + (dired-rmail))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Put `mouse-face' on whole line, not just file name. +;; 2. Add text property `dired-filename' to only the file name. +;; 3. Show image-file preview on mouseover, if `tooltip-mode' +;; and if `diredp-image-preview-in-tooltip'. +;; +(defun dired-insert-set-properties (beg end) + "Add various text properties to the lines in the region. +Highlight entire line upon mouseover. +Add text property `dired-filename' to the file name. +Handle `dired-hide-details-mode' invisibility spec (Emacs 24.4+)." + (let ((inhibit-field-text-motion t)) ; Just in case. + (save-excursion + (goto-char beg) + (while (< (point) end) + (condition-case nil + (cond ((dired-move-to-filename) + (add-text-properties (line-beginning-position) (line-end-position) + '(mouse-face highlight help-echo diredp-mouseover-help)) + (put-text-property + (point) (save-excursion (dired-move-to-end-of-filename) (point)) + 'dired-filename t) + (when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (put-text-property (+ (line-beginning-position) 1) (1- (point)) + 'invisible 'dired-hide-details-detail) + (dired-move-to-end-of-filename) + (when (< (+ (point) 4) (line-end-position)) + (put-text-property (+ (point) 4) (line-end-position) + 'invisible 'dired-hide-details-link)))) + ((fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (unless (or (diredp-looking-at-p "^$") (diredp-looking-at-p dired-subdir-regexp)) + (put-text-property (line-beginning-position) (1+ (line-end-position)) + 'invisible 'dired-hide-details-information)))) + (error nil)) + (forward-line 1))))) + +(defun diredp-mouseover-help (window buffer pos) + "Show `help-echo' help for a file name, in Dired. +If `tooltip-mode' is on and `diredp-image-preview-in-tooltip' says to +show an image preview, then do so. Otherwise, show text help." + (let ((image-dired-thumb-width (or (and (wholenump diredp-image-preview-in-tooltip) + diredp-image-preview-in-tooltip) + image-dired-thumb-width)) + (image-dired-thumb-height (or (and (wholenump diredp-image-preview-in-tooltip) + diredp-image-preview-in-tooltip) + image-dired-thumb-height)) + file) + (or (and (boundp 'tooltip-mode) tooltip-mode + (fboundp 'image-file-name-regexp) ; Emacs 22+, `image-file.el'. + diredp-image-preview-in-tooltip + (condition-case nil + (and (with-current-buffer buffer + (save-excursion (goto-char pos) + (diredp-string-match-p + (image-file-name-regexp) + (setq file (if (derived-mode-p 'dired-mode) + (dired-get-filename nil 'NO-ERROR) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) + (line-end-position))))))) + (or (not diredp-auto-focus-frame-for-thumbnail-tooltip-flag) + (progn (select-frame-set-input-focus (window-frame window)) t)) + (let ((img-file (if (eq 'full diredp-image-preview-in-tooltip) + file + (diredp-image-dired-create-thumb file)))) + (propertize " " 'display (create-image img-file)))) + (error nil))) + (if (fboundp 'describe-file) ; Library `help-fns+.el' + "mouse-2: visit in another window, C-h RET: describe" + "mouse-2: visit this file/dir in another window")))) + +;; `dired-hide-details-mode' enhancements. +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + + (defun diredp-hide-details-if-dired () + "In Dired mode hide details. Outside Dired, do nothing." + (when (derived-mode-p 'dired-mode) (dired-hide-details-mode 1))) + + ;; Use `eval' of list so file byte-compiled in Emacs 20 will be OK in later versions. + (eval '(define-globalized-minor-mode global-dired-hide-details-mode + dired-hide-details-mode diredp-hide-details-if-dired)) + + (eval '(define-minor-mode dired-hide-details-mode + "Hide details in Dired mode." + (and diredp-hide-details-propagate-flag diredp-hide-details-last-state) + :group 'dired + (unless (derived-mode-p 'dired-mode) (error "Not a Dired buffer")) + (dired-hide-details-update-invisibility-spec) + (setq diredp-hide-details-toggled t) + (when diredp-hide-details-propagate-flag + (setq diredp-hide-details-last-state dired-hide-details-mode)) + (if dired-hide-details-mode + (add-hook 'wdired-mode-hook 'dired-hide-details-update-invisibility-spec nil t) + (remove-hook 'wdired-mode-hook 'dired-hide-details-update-invisibility-spec t)))) + + (defun diredp-hide/show-details () + "Hide/show details according to user options. +If `diredp-hide-details-propagate-flag' is non-nil and details have +never been hidden in the buffer, then hide/show according to your last +hide/show choice in any other Dired buffer or, if no last choice, +according to option `diredp-hide-details-initially-flag'." + (unless (or diredp-hide-details-toggled ; No op if hide/show already set. + (buffer-narrowed-p)) ; No-op when showing just newly copied file etc. + (cond (diredp-hide-details-propagate-flag + (dired-hide-details-mode (if diredp-hide-details-last-state 1 -1))) + (diredp-hide-details-initially-flag + (dired-hide-details-mode 1))))) + + (add-hook 'dired-after-readin-hook #'diredp-hide/show-details) + + (defun diredp-fit-frame-unless-buffer-narrowed () + "Fit frame unless Dired buffer is narrowed. +Requires library `autofit-frame.el'." + (when (and (get-buffer-window (current-buffer)) (not (buffer-narrowed-p))) + (fit-frame-if-one-window))) + + ;; Fit frame only if not narrowed. Put it on this hook because `dired-hide-details-mode' is + ;; invoked from `dired-after-readin-hook' via `diredp-hide/show-details', even for an update + ;; such as copying a file, where buffer is narrowed when invoked. + (when (fboundp 'fit-frame-if-one-window) ; In `autofit-frame.el'. + (add-hook 'dired-hide-details-mode-hook #'diredp-fit-frame-unless-buffer-narrowed))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Reset `mode-line-process' to nil. +;; +(when (< emacs-major-version 21) + (or (fboundp 'old-dired-revert) (fset 'old-dired-revert (symbol-function 'dired-revert))) + (defun dired-revert (&optional arg noconfirm) + (setq mode-line-process nil) ; Set by, e.g., `find-dired'. + (old-dired-revert arg noconfirm))) + +;; Like `dired-up-directory', but go up to MS Windows drive if in top-level directory. +;; +;;;###autoload +(defun diredp-up-directory (&optional other-window) ; Bound to `^' + "Run Dired on parent directory of current directory. +Find the parent directory either in this buffer or another buffer. +Creates a buffer if necessary. + +With a prefix arg, Dired the parent directory in another window. + +On MS Windows, if you are already at the root directory, invoke +`diredp-w32-drives' to visit a navigable list of Windows drives." + (interactive "P") + (let* ((dir (dired-current-directory)) + (up (file-name-directory (directory-file-name dir)))) + (or (dired-goto-file (directory-file-name dir)) + ;; Only try `dired-goto-subdir' if buffer has more than one dir. + (and (cdr dired-subdir-alist) (dired-goto-subdir up)) + (progn (if other-window (dired-other-window up) (dired up)) + (dired-goto-file dir)) + (and (memq system-type '(windows-nt ms-dos)) (diredp-w32-drives other-window))))) + +;;;###autoload +(defun diredp-up-directory-reuse-dir-buffer (&optional other-window) ; Not bound + "Like `diredp-up-directory', but reuse Dired buffers. +With a prefix arg, Dired the parent directory in another window. + +On MS Windows, moving up from a root Dired buffer does not kill that +buffer (the Windows drives buffer is not really a Dired buffer)." + (interactive "P") + (let* ((dir (dired-current-directory)) + (dirfile (directory-file-name dir)) + (up (file-name-directory dirfile))) + (or (dired-goto-file dirfile) + ;; Only try `dired-goto-subdir' if buffer has more than one dir. + (and (cdr dired-subdir-alist) (dired-goto-subdir up)) ; It is a subdir inserted in current Dired. + (progn (diredp--reuse-dir-buffer-helper up nil nil other-window) + (dired-goto-file dir)) + (and (memq system-type '(windows-nt ms-dos)) (diredp-w32-drives other-window))))) + +;; Differs from `dired-next-line' in both wraparound and respect of `goal-column'. +;; +;;;###autoload +(defun diredp-next-line (arg) ; Bound to `SPC', `n', `C-n', `down' + "Move down lines then position cursor at filename. +If `goal-column' is non-nil then put the cursor at that column. +Optional prefix ARG says how many lines to move; default is one line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer end (buffer beginning, if ARG is negative). +Otherwise, just move to the buffer limit." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (let* ((line-move-visual nil) + ;; (goal-column nil) + + ;; Use `condition-case' and `(progn... t)' because Emacs < 22 `line-move' has no + ;; NO-ERROR arg and it always returns nil. + (no-more (or (not (condition-case nil (progn (line-move arg) t) (error nil))) + (if (< arg 0) (bobp) (eobp))))) + (when (and diredp-wrap-around-flag no-more) + (let ((diredp-wrap-around-flag nil)) + (goto-char (if (< arg 0) (point-max) (point-min))) + (diredp-next-line arg))) + ;; We never want to move point into an invisible line. + (while (and (fboundp 'invisible-p) ; Emacs 22+ + (invisible-p (point)) + (not (if (and arg (< arg 0)) (bobp) (eobp)))) + (forward-char (if (and arg (< arg 0)) -1 1))) + (unless goal-column (dired-move-to-filename)))) + +;; In Emacs < 22, `C-p' does not wrap around, because it never moves to the first header line. +;;;###autoload +(defun diredp-previous-line (arg) ; Bound to `p', `C-p', `up' + "Move up lines then position cursor at filename. +If `goal-column' is non-nil then put the cursor at that column. +Optional prefix ARG says how many lines to move; default is one line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer beginning (buffer end, if ARG is negative). +Otherwise, just move to the buffer limit." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (diredp-next-line (- (or arg 1)))) + +;;;###autoload +(defun diredp-next-dirline (arg &optional opoint) ; Bound to `>' + "Goto ARGth next directory file line. +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer beginning (buffer end, if ARG is negative). +Otherwise, raise an error or, if NO-ERROR-IF-NOT-FOUND is nil, return +nil." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (or opoint (setq opoint (point))) + (if (if (> arg 0) + (re-search-forward dired-re-dir nil t arg) + (beginning-of-line) + (re-search-backward dired-re-dir nil t (- arg))) + (dired-move-to-filename) ; user may type `i' or `f' + (if diredp-wrap-around-flag + (let ((diredp-wrap-around-flag nil)) + (goto-char (if (< arg 0) (point-max) (point-min))) + (diredp-next-dirline arg opoint)) + (goto-char opoint) + (error "No more subdirectories")))) + +;;;###autoload +(defun diredp-prev-dirline (arg) ; Bound to `<' + "Goto ARGth previous directory file line." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (diredp-next-dirline (- arg))) + +;;;###autoload +(defun diredp-next-subdir (arg &optional no-error-if-not-found no-skip) ; Bound to `C-M-n' + "Go to the next subdirectory, regardless of level. +If ARG = 0 then go to this directory's header line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer end (buffer beginning, if ARG is negative). +Otherwise, raise an error or, if NO-ERROR-IF-NOT-FOUND is nil, return +nil. + +Non-nil NO-SKIP means do not move to end of header line, and return +the position moved to so far." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (let ((this-dir (dired-current-directory)) + pos index) + ;; `nth' with negative arg does not return nil but the first element + (setq index (if diredp-wrap-around-flag + (mod (- (dired-subdir-index this-dir) arg) (length dired-subdir-alist)) + (- (dired-subdir-index this-dir) arg)) + pos (and (>= index 0) (dired-get-subdir-min (nth index dired-subdir-alist)))) + (if pos + (progn (goto-char pos) + (or no-skip (skip-chars-forward "^\n\r")) + (point)) + (if no-error-if-not-found + nil ; Return nil if not found + (error "%s directory" (if (> arg 0) "Last" "First")))))) + +;;;###autoload +(defun diredp-prev-subdir (arg &optional no-error-if-not-found no-skip) ; Bound to `C-M-p' + "Go to the previous subdirectory, regardless of level. +When called interactively and not on a subdir line, go to this subdir's line. +Otherwise, this is a mirror image of `diredp-next-subdir'." + ;;(interactive "^p") + (interactive + (list (if current-prefix-arg + (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + narg) ; Equivalent to "^p" + ;; If on subdir start already then do not stay there. + (if (dired-get-subdir) 1 0)))) + (diredp-next-subdir (- arg) no-error-if-not-found no-skip)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Test also ./ and ../, in addition to . and .., for error "Cannot operate on `.' or `..'". +;; 2. Hack for Emacs 20-22, to expand `~/...'. +;; +(defun dired-get-filename (&optional localp no-error-if-not-filep) + "In Dired, return name of file mentioned on this line. +Value returned normally includes the directory name. + +Optional arg LOCALP: + `no-dir' means do not include directory name in result. + `verbatim' means return the name exactly as it occurs in the buffer. + Any other non-nil value means construct the name relative to + `default-directory', which still might contain slashes if point is + in a subdirectory. + +Non-nil optional arg NO-ERROR-IF-NOT-FILEP means treat `.' and `..' as +regular filenames and return nil if there is no filename on this line. +Otherwise, an error occurs in these cases." + (let ((case-fold-search nil) + (already-absolute nil) + file p1 p2) + (save-excursion (when (setq p1 (dired-move-to-filename (not no-error-if-not-filep))) + (setq p2 (dired-move-to-end-of-filename no-error-if-not-filep)))) + ;; nil if no file on this line but `no-error-if-not-filep' is t: + (when (setq file (and p1 p2 (buffer-substring p1 p2))) + ;; Get rid of the mouse-face property that file names have. + (set-text-properties 0 (length file) nil file) + + ;; Unquote names quoted by `ls' or by `dired-insert-directory'. + ;; Prior to Emacs 23.3, this code was written using `read' (see commented code below), + ;; because that is faster than substituting \007 (4 chars) -> ^G (1 char) etc. in a loop. + ;; Unfortunately, that implementation required hacks such as dealing with filenames + ;; with quotation marks in their names. + (while (string-match (if (> emacs-major-version 21) + "\\(?:[^\\]\\|\\`\\)\\(\"\\)" ; Shy group: Emacs 22+. + "\\([^\\]\\|\\`\\)\\(\"\\)") + file) + (setq file (replace-match "\\\"" nil t file 1))) + + ;; $$$ This was the code for that unquoting prior to Emacs 23.3: + ;; (setq file (read (concat "\"" ; Some `ls -b' do not escape quotes. But GNU `ls' is OK. + ;; (or (dired-string-replace-match + ;; "\\([^\\]\\|\\`\\)\"" file "\\1\\\\\"" nil t) + ;; file) + ;; "\""))) + + ;; This sexp was added by Emacs 24, to fix bug #10469: + ;; Unescape any spaces escaped by `ls -b'. + ;; Other `-b' quotes, such as \t and \n, work transparently. + (when (dired-switches-escape-p dired-actual-switches) + (let ((start 0) + (rep "") + (shift -1)) + (when (eq localp 'verbatim) (setq rep "\\\\" + shift +1)) + (while (string-match "\\(\\\\\\) " file start) + (setq file (replace-match rep nil t file 1) + start (+ shift (match-end 0)))))) + + ;; $$$ This sexp was added by Emacs 23.3. + (when (memq system-type '(windows-nt ms-dos)) + (save-match-data + (let ((start 0)) + (while (string-match "\\\\" file start) + (aset file (match-beginning 0) ?/) + (setq start (match-end 0)))))) + + ;; $$$ This sexp was added by Emacs 23.3. + ;; Hence we don't need to worry about converting `\\' back to `\'. + (setq file (read (concat "\"" file "\""))) + + ;; Above `read' returns a unibyte string if FILE contains eight-bit-control/graphic chars. + (when (and (fboundp 'string-to-multibyte) ; Emacs 22 + enable-multibyte-characters + (not (multibyte-string-p file))) + (setq file (string-to-multibyte file)))) + (and file + (file-name-absolute-p file) + ;; A relative file name can start with ~. Do not treat it as absolute in this context. + (not (eq (aref file 0) ?~)) + (setq already-absolute t)) + (cond ((null file) nil) + ((eq localp 'verbatim) file) + ;; This is the essential `Dired+' change: Added ./ and ../, not just . and .. + ((and (not no-error-if-not-filep) (member file '("." ".." "./" "../"))) + (error "Cannot operate on `.' or `..'")) + ((and (eq localp 'no-dir) already-absolute) + (file-name-nondirectory file)) + (already-absolute + (let ((handler (find-file-name-handler file nil))) + ;; check for safe-magic property so that we won't + ;; put /: for names that don't really need them. + ;; For instance, .gz files when auto-compression-mode is on. + (if (and handler (not (get handler 'safe-magic))) + (concat "/:" file) + file))) + ((eq localp 'no-dir) file) + ((equal (dired-current-directory) "/") + (setq file (concat (dired-current-directory localp) file)) + (let ((handler (find-file-name-handler file nil))) + ;; check for safe-magic property so that we won't + ;; put /: for names that don't really need them. + ;; For instance, .gz files when auto-compression-mode is on. + (if (and handler (not (get handler 'safe-magic))) + (concat "/:" file) + file))) + ;; Ugly hack for Emacs < 23, for which `ls-lisp-insert-directory' can insert a subdir + ;; using `~/...'. Expand `~/' for return value. + ((and (< emacs-major-version 23) file (file-name-absolute-p file) + (eq (aref file 0) ?~)) + (expand-file-name file)) + (t + (concat (dired-current-directory localp) file))))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Fixes Emacs bug #7126: Did not work with arbitrary file list (cons arg to `dired'). +;; 2. Remove `/' from directory name before comparing with BASE. +;; +(when (< emacs-major-version 24) + (defun dired-goto-file (file) ; Bound to `j' + "Go to line describing file FILE in this Dired buffer. +FILE must be an absolute file name. +Return buffer position on success, else nil." + ;; Loses if FILE contains control chars like "\007" for which `ls' inserts "?" or "\\007" + ;; into the buffer, so we won't find it in the buffer. + (interactive (prog1 ; Let push-mark display its message + (list (expand-file-name (read-file-name "Goto file: " (dired-current-directory)))) + (push-mark))) + (unless (file-name-absolute-p file) (error "File name `%s' is not absolute" file)) + (setq file (directory-file-name file)) ; does no harm if no directory + (let* ((case-fold-search nil) + (dir (file-name-directory file)) + (found nil)) + ;; `Dired+': Added this sexp. + (save-excursion + (goto-char (point-min)) + (let ((search-string (replace-regexp-in-string "\^m" "\\^m" file nil t)) + (here nil)) + (setq search-string (replace-regexp-in-string "\\\\" "\\\\" search-string nil t)) + + ;; Escape whitespace. Added per Emacs 24 addition in `unless' code below: + (when (and (dired-switches-escape-p dired-actual-switches) + (diredp-string-match-p "[ \t\n]" search-string)) + ;; FIXME: fix this for all possible file names (embedded control chars etc). + ;; Need to escape everything that `ls -b' escapes. + (setq search-string (replace-regexp-in-string " " "\\ " search-string nil t) + search-string (replace-regexp-in-string "\t" "\\t" search-string nil t) + search-string (replace-regexp-in-string "\n" "\\n" search-string nil t))) + + ;; Use HERE to ensure we do not keep searching for a directory entry. + (while (and (not (eobp)) (not found) (not (equal here (point)))) + (setq here (point)) + (if (search-forward (concat " " search-string) nil 'NO-ERROR) + ;; Must move to filename since an (actually correct) match could have been + ;; elsewhere on the line (e.g. "-" would match somewhere in permission bits). + (setq found (dired-move-to-filename)) + ;; If this isn't the right line, move forward to avoid trying this line again. + (forward-line 1))))) + + (unless found + (save-excursion + ;; The difficulty here is to get the result of `dired-goto-subdir' without really + ;; calling it, if we don't have any subdirs. + (when (if (string= dir (expand-file-name default-directory)) + (goto-char (point-min)) + (and (cdr dired-subdir-alist) (dired-goto-subdir dir))) + (let ((base (file-name-nondirectory file)) + (boundary (dired-subdir-max)) + search-string) + (setq search-string (replace-regexp-in-string "\^m" "\\^m" base nil t) + search-string (replace-regexp-in-string "\\\\" "\\\\" search-string nil t)) + + ;; Escape whitespace. Sexp added by Emacs 24: + (when (and (dired-switches-escape-p dired-actual-switches) + (diredp-string-match-p "[ \t\n]" search-string)) + ;; FIXME: fix this for all possible file names (embedded control chars etc). + ;; Need to escape everything that `ls -b' escapes. + (setq search-string (replace-regexp-in-string " " "\\ " search-string nil t) + search-string (replace-regexp-in-string "\t" "\\t" search-string nil t) + search-string (replace-regexp-in-string "\n" "\\n" search-string nil t))) + (while (and (not found) + ;; Filenames are preceded by SPC. This makes the search faster + ;; (e.g. for the filename "-"!). + (search-forward (concat " " search-string) boundary 'move)) + ;; `Dired+': Remove `/' from filename, then compare with BASE. + ;; Match could have BASE just as initial substring or + ;; or in permission bits or date or not be a proper filename at all. + (if (and (dired-get-filename 'no-dir t) + (equal base (directory-file-name (dired-get-filename 'no-dir t)))) + ;; Must move to filename since an (actually correct) match could have been + ;; elsewhere on the line (e.g. "-" would match somewhere in permission bits). + (setq found (dired-move-to-filename)) + ;; If this is not the right line, move forward to avoid trying this line again. + (forward-line 1))))))) + (and found (goto-char found))))) ; Return buffer position, or nil if not found. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; If destination is in a hidden dir listing, open that listing and move to destination in it. +;; +(unless (< emacs-major-version 24) + (defun dired-goto-file (file) + "Go to line describing file FILE in this Dired buffer. +FILE must be an absolute file name. +Return buffer position on success, else nil." + ;; Loses if FILE contains control chars like "\007" for which `ls' inserts "?" or "\\007" + ;; into the buffer, so we won't find it in the buffer. + (interactive (prog1 (list (expand-file-name (read-file-name "Goto file: " (dired-current-directory)))) + (push-mark))) ; Let push-mark display its message. + (unless (file-name-absolute-p file) (error "File name `%s' is not absolute" file)) + (setq file (directory-file-name file)) ; Does no harm if not a directory + (let* ((case-fold-search nil) + (dir (file-name-directory file)) + (found + (or + ;; First, look for a listing under the absolute name. + (save-excursion (goto-char (point-min)) (dired-goto-file-1 file file (point-max))) + ;; Else look for it as a relative name. The difficulty is to get the result + ;; of `dired-goto-subdir' without calling it, if we don't have any subdirs. + (save-excursion + (when (if (string= dir (expand-file-name default-directory)) + (goto-char (point-min)) + (and (cdr dired-subdir-alist) (dired-goto-subdir dir))) + (when (dired-subdir-hidden-p (dired-current-directory)) + (diredp-hide-subdir-nomove 1)) ; Open hidden parent directory. + (dired-goto-file-1 (file-name-nondirectory file) file (dired-subdir-max))))))) + (and found (goto-char found))))) ; Return buffer position, or nil if not found. + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Display a message to warn that flagged, not marked, files will be deleted. +;; 2. Use `diredp-internal-do-deletions', so it works with all Emacs versions. +;; +;;;###autoload +(defun dired-do-flagged-delete (&optional no-msg) ; Bound to `x' + "In Dired, delete the files flagged for deletion. +NOTE: This deletes flagged, not marked, files. +If arg NO-MSG is non-nil, no message is displayed. + +User option `dired-recursive-deletes' controls whether deletion of +non-empty directories is allowed." + (interactive) + (unless no-msg + (ding) + (message "NOTE: Deletion of files flagged `%c' (not those marked `%c')" + dired-del-marker dired-marker-char) + ;; Too slow/annoying, but without it the message is never seen: (sit-for 2) + ) + (let* ((dired-marker-char dired-del-marker) + (regexp (dired-marker-regexp)) + (case-fold-search nil)) + (if (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t)) + (diredp-internal-do-deletions + ;; This cannot move point since last arg is nil. + (dired-map-over-marks (cons (dired-get-filename) (point)) nil) + nil + 'USE-TRASH-CAN) ; This arg is for Emacs 24+ only. + (unless no-msg (message "(No deletions requested.)"))))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Display a message to warn that marked, not flagged, files will be deleted. +;; 2. Use `diredp-internal-do-deletions', so it works with all Emacs versions. +;; +;;;###autoload +(defun dired-do-delete (&optional arg) ; Bound to `D' + "Delete all marked (or next ARG) files. +NOTE: This deletes marked, not flagged, files. +`dired-recursive-deletes' controls whether deletion of +non-empty directories is allowed." + (interactive "P") + ;; This is more consistent with the file-marking feature than + ;; `dired-do-flagged-delete'. But it can be confusing to the user, + ;; especially since this is usually bound to `D', which is also the + ;; `dired-del-marker'. So offer this warning message: + (unless arg + (ding) + (message "NOTE: Deletion of files marked `%c' (not those flagged `%c')." + dired-marker-char dired-del-marker)) + (diredp-internal-do-deletions + ;; This can move point if ARG is an integer. + (dired-map-over-marks (cons (dired-get-filename) (point)) arg) + arg + 'USE-TRASH-CAN)) ; This arg is for Emacs 24+ only. + +(defun diredp-internal-do-deletions (file-alist arg &optional trash) + "`dired-internal-do-deletions', but for any Emacs version. +FILE-ALIST is an alist of files to delete, with their buffer positions. +ARG is the prefix arg. Filenames are absolute. +Non-nil TRASH means use the trash can." + ;; \(car FILE-ALIST) *must* be the *last* (bottommost) file in the dired + ;; buffer. That way as changes are made in the buffer they do not shift + ;; the lines still to be changed, so the (point) values in FILE-ALIST + ;; stay valid. Also, for subdirs in natural order, a subdir's files are + ;; deleted before the subdir itself - the other way around would not work." + (setq file-alist (delq nil file-alist)) ; nils could come from `dired-map-over-marks'. + (if (> emacs-major-version 23) + (dired-internal-do-deletions file-alist arg trash) + (dired-internal-do-deletions file-alist arg))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Put window point at bob. Fixes bug #12281. +;; +(when (and (> emacs-major-version 22) (or (< emacs-major-version 24) + (and (= emacs-major-version 24) (= emacs-minor-version 1)))) + (defun dired-pop-to-buffer (buf) + "Pop up buffer BUF in a way suitable for Dired." + (let ((split-window-preferred-function + (lambda (window) + (or (and (let ((split-height-threshold 0)) (window-splittable-p (selected-window))) + ;; Try to split the selected window vertically if that's possible. (Bug#1806) + (if (fboundp 'split-window-below) (split-window-below) (split-window-vertically))) + (split-window-sensibly window)))) + pop-up-frames) + (pop-to-buffer (get-buffer-create buf))) + (set-window-start (selected-window) (point-min)) + (when dired-shrink-to-fit + ;; Try to not delete window when we want to display less than `window-min-height' lines. + (fit-window-to-buffer (get-buffer-window buf) nil 1)))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Delete the window or frame popped up, afterward, and bury its buffer. +;; Fixes Emacs bug #7533. +;; +;; 2, If buffer is shown in a separate frame, do not show a menu bar for that frame. +;; +(defun dired-mark-pop-up (buffer-or-name op-symbol files function &rest args) + "Return FUNCTION's result on ARGS after showing which files are marked. +Displays the file names in a buffer named BUFFER-OR-NAME, the default +name being \" *Marked Files*\". The buffer is not shown if there is +just one file, `dired-no-confirm' is t, or OP-SYMBOL is a member of +the list in `dired-no-confirm'. Uses function `dired-pop-to-buffer' +to show the buffer. + +The window is not shown if there is just one file, `dired-no-confirm' +is `t', or OP-SYMBOL is a member of `dired-no-confirm'. + +FILES is the list of marked files. It can also be (t FILENAME) +in the case of one marked file, to distinguish that from using +just the current file. + +FUNCTION should not manipulate the files. It should just read input +\(an argument or confirmation)." + (unless buffer-or-name (setq buffer-or-name " *Marked Files*")) + (let (result) + (if (or (eq dired-no-confirm t) + (memq op-symbol dired-no-confirm) + ;; If FILES defaulted to the current line's file. + (= (length files) 1)) + (setq result (apply function args)) + (with-current-buffer (get-buffer-create buffer-or-name) + (erase-buffer) + ;; Handle (t FILE) just like (FILE), here. That value is used (only in some cases), + ;; to mean just one file that was marked, rather than the current-line file. + (dired-format-columns-of-files (if (eq (car files) t) (cdr files) files)) + (remove-text-properties (point-min) (point-max) + '(mouse-face nil help-echo nil))) + (unwind-protect + (save-window-excursion + ;; Do not show menu bar, if buffer is popped up in a separate frame. + (let ((special-display-frame-alist (cons '(menu-bar-lines . 0) + special-display-frame-alist)) + (default-frame-alist (cons '(menu-bar-lines . 0) + default-frame-alist))) + (dired-pop-to-buffer buffer-or-name) + ;; Work around Emacs 22 bug in `dired-pop-to-buffer', which can exit with Dired buffer current. + (set-buffer buffer-or-name) + (goto-char (point-min))) + (setq result (apply function args))) + (save-excursion + (condition-case nil ; Ignore error if user already deleted window. + (progn (select-window (get-buffer-window buffer-or-name 0)) + (if (one-window-p) (delete-frame) (delete-window))) + (error nil))) + (bury-buffer buffer-or-name))) + result)) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Prefix arg has more possibilities. +;; 2, Added optional arg LOCALP, so you can mark/unmark matching different file-name forms. +;; 3. Push REGEXP onto `regexp-search-ring'. +;; +;;;###autoload +(defun dired-mark-files-regexp (regexp &optional marker-char localp) + "Mark all file names matching REGEXP for use in later commands. +`.' and `..' are never marked or unmarked by this command. + +Whether to mark or unmark, and what form of file name to match, are +governed by the prefix argument. For this, a plain (`C-u') or a +double-plain (`C-u C-u') prefix arg is considered only as such - it is +not considered numerically. + +Whether to mark or unmark: + + - No prefix arg, a positive arg, or a negative arg means mark. + + - Plain (`C-u'), double-plain (`C-u C-u'), or zero (e.g. `M-0' means + unmark. + +The form of a file name used for matching: + + - No prefix arg (to mark) or a plain prefix arg (`C-u', to unmark) + means use the relative file name (no directory part). + + - A negative arg (e.g. `M--', to mark) or a zero arg (e.g. `M-0', to + unmark) means use the absolute file name, that is, including all + directory components. + + - A positive arg (e.g. `M-+', to mark) or a double plain arg (`C-u + C-u', to unmark) means construct the name relative to + `default-directory'. For an entry in an inserted subdir listing, + this means prefix the relative file name (no directory part) with + the subdir name relative to `default-directory'. + +Note that the default matching behavior of this command is different +for Dired+ than it is for vanilla Emacs. Using a positive prefix arg +or a double plain prefix arg (`C-u C-u') gives you the same behavior +as vanilla Emacs (marking or unmarking, respectively): matching +against names that are relative to the `default-directory'. + +What Dired+ offers in addition is the possibility to match against +names that are relative (have no directory part - no prefix arg or +`C-u' to mark and unmark, respectively) or absolute (`M--' or `M-0', +respectively). The default behavior uses relative names because this +is likely to be the more common use case. But matching against +absolute names gives you more flexibility. + +REGEXP is an Emacs regexp, not a shell wildcard. Thus, use `\\.o$' +for object files--just `.o' might mark more than you might expect. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Non-interactively: + MARKER-CHAR is the marker character - used for `dired-marker-char'. + LOCALP is passed to `dired-get-filename'. It determines the form of + filename that is matched against REGEXP." + (interactive (let* ((raw current-prefix-arg) + (C-u (and (consp raw) (= 4 (car raw)))) + (C-u-C-u (and (consp raw) (= 16 (car raw)))) + (num (and raw (prefix-numeric-value raw)))) + (list (diredp-read-regexp (concat (if (or (consp raw) (and num (zerop num))) + "UNmark" + "Mark") + " files (regexp): ")) + (and raw (or C-u C-u-C-u (zerop num)) ?\040) + (cond ((or (not raw) C-u) t) ; none, `C-u' + ((> num 0) nil) ; `M-+', `C-u C-u' + (t 'no-dir))))) ; `M--', `M-0' + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) ; Empty line + (let ((fn (dired-get-filename localp t))) + (and fn (diredp-string-match-p regexp fn)))) + "file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-files-containing-regexp (regexp &optional marker-char) + "Mark files with contents containing a REGEXP match. +A prefix argument means unmark them instead. +`.' and `..' are never marked. + +If a file is visited in a buffer and `dired-always-read-filesystem' is +nil, this looks in the buffer without revisiting the file, so the +results might be inconsistent with the file on disk if its contents +have changed since it was last visited." + (interactive + (list (diredp-read-regexp (concat (if current-prefix-arg "Unmark" "Mark") " files containing (regexp): ") + nil 'dired-regexp-history) + (and current-prefix-arg ?\040))) + (let ((dired-marker-char (or marker-char dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (when (and fname (file-readable-p fname) (not (file-directory-p fname))) + (let ((prebuf (get-file-buffer fname))) + (message "Checking %s" fname) + ;; For now, do it inside Emacs. Grep might be better if there are lots of files. + (if (and prebuf (or (not (boundp 'dired-always-read-filesystem)) + (not dired-always-read-filesystem))) ; Emacs 26+ + (with-current-buffer prebuf + (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t))) + (with-temp-buffer + (insert-file-contents fname) + (goto-char (point-min)) + (re-search-forward regexp nil t))))))) + "file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-symlinks (unflag-p) + "Mark all symbolic links. +With prefix argument, unmark or unflag all those files." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (diredp-looking-at-p dired-re-sym) "symbolic link"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-directories (unflag-p) + "Mark all directory file lines except `.' and `..'. +With prefix argument, unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (and (diredp-looking-at-p dired-re-dir) (not (diredp-looking-at-p dired-re-dot))) + "directory" "directories"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-executables (unflag-p) + "Mark all executable files. +With prefix argument, unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (diredp-looking-at-p dired-re-exe) "executable file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-flag-auto-save-files (&optional unflag-p) + "Flag for deletion files whose names suggest they are auto save files. +A prefix argument says to unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-del-marker))) + (diredp-mark-if + ;; It is less than general to check for # here, but it's the only way this runs fast enough. + (and (save-excursion (end-of-line) + (or (eq (preceding-char) ?#) + ;; Handle executables in case of -F option. Need not worry about the other kinds + ;; of markings that -F makes, since they won't appear on real auto-save files. + (and (eq (preceding-char) ?*) + (progn (forward-char -1) (eq (preceding-char) ?#))))) + (not (diredp-looking-at-p dired-re-dir)) + (let ((fname (dired-get-filename t t))) + (and fname (auto-save-file-name-p (file-name-nondirectory fname))))) + "auto-save file"))) + +;;;###autoload +(defun diredp-capitalize (&optional arg) ; Bound to `% c' + "Rename all marked (or next ARG) files by capitalizing them. +Makes the first char of the name uppercase and the others lowercase." + (interactive "P") + (dired-rename-non-directory #'capitalize "Rename by capitalizing:" arg)) + +;; This is more useful than a single-file version of `dired-do-delete'. +;;;###autoload +(defun diredp-delete-this-file (&optional use-trash-can) ; Bound to `C-k', `delete' + "In Dired, delete the file on the cursor line, upon confirmation. +This uses `delete-file'. +If the file is a symlink, remove the symlink. If the file has +multiple names, it continues to exist with the other names. + +For Emacs 24 and later, a prefix arg means that if +`delete-by-moving-to-trash' is non-nil then trash the file instead of +deleting it." + (interactive "P") + (let ((file (dired-get-filename))) + (if (not (yes-or-no-p (format "%s file `%s'? " (if (and use-trash-can delete-by-moving-to-trash) + "Trash" + "Permanently delete") + file))) + (message "OK - canceled") + (if (> emacs-major-version 23) (delete-file file use-trash-can) (delete-file file)) + (revert-buffer)))) + +;;; Versions of `dired-do-*' commands for just this line's file. +;;;###autoload +(defun diredp-capitalize-this-file () ; Bound to `M-c' + "In Dired, rename the file on the cursor line by capitalizing it. +Makes the first char of the name uppercase and the others lowercase." + (interactive) (diredp-capitalize 1)) + +;;;###autoload +(defun diredp-downcase-this-file () ; Bound to `M-l' + "In Dired, rename the file on the cursor line to lower case." + (interactive) (dired-downcase 1)) + +;;;###autoload +(defun diredp-upcase-this-file () ; Bound to `M-u' + "In Dired, rename the file on the cursor line to upper case." + (interactive) (dired-upcase 1)) + +;;;###autoload +(defun diredp-rename-this-file () ; Bound to `r' + "In Dired, rename the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-rename 1))) + +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defun diredp-decrypt-this-file () + "In Dired, decrypt the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-decrypt 1))) + + (defun diredp-encrypt-this-file () + "In Dired, encrypt the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-encrypt 1))) + + (defun diredp-verify-this-file () + "In Dired, verify the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-verify 1))) + + (defun diredp-sign-this-file () + "In Dired, sign the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-sign 1)))) + +;;;###autoload +(defun diredp-copy-this-file () ; Not bound + "In Dired, copy the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-copy 1))) + +;;;###autoload +(defun diredp-relsymlink-this-file () ; Bound to `y' + "In Dired, make a relative symbolic link to file on cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-relsymlink 1))) + +;;;###autoload +(defun diredp-symlink-this-file () ; Not bound + "In Dired, make a symbolic link to the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-symlink 1))) + +;;;###autoload +(defun diredp-hardlink-this-file () ; Not bound + "In Dired, add a name (hard link) to the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-hardlink 1))) + +;;;###autoload +(defun diredp-print-this-file () ; Bound to `M-p' + "In Dired, print the file on the cursor line." + (interactive) (dired-do-print 1)) + +;;;###autoload +(defun diredp-grep-this-file () ; Not bound + "In Dired, grep the file on the cursor line." + (interactive) + (unless (and grep-command (or (< emacs-major-version 22) + (not grep-use-null-device) + (eq grep-use-null-device t))) + (grep-compute-defaults)) + (grep (diredp-do-grep-1 (list (dired-get-filename t))))) + +;;;###autoload +(defun diredp-compress-this-file () ; Bound to `z' + "In Dired, compress or uncompress the file on the cursor line." + (interactive) (dired-do-compress 1)) + +;;;###autoload +(defun diredp-async-shell-command-this-file (command filelist) ; Not bound + "Run a shell COMMAND asynchronously on the file on the Dired cursor line. +Like `diredp-shell-command-this-file', but adds `&' at the end of +COMMAND to execute it asynchronously. The command output appears in +buffer `*Async Shell Command*'." + (interactive (list (dired-read-shell-command (concat "& on " "%s: ") 1 (list (dired-get-filename t))) + (list (dired-get-filename t)))) + (unless (diredp-string-match-p "&[ \t]*\\'" command) (setq command (concat command " &"))) + (dired-do-shell-command command 1 filelist)) + +;;;###autoload +(defun diredp-shell-command-this-file (command filelist) ; Not bound + "In Dired, run a shell COMMAND on the file on the cursor line." + (interactive (list (dired-read-shell-command (concat "! on " "%s: ") 1 (list (dired-get-filename t))) + (list (dired-get-filename t)))) + (dired-do-shell-command command 1 filelist)) + +;;;###autoload +(defun diredp-bookmark-this-file (&optional prefix) ; Bound to `C-B' (`C-S-b') + "In Dired, bookmark the file on the cursor line. +See `diredp-do-bookmark'." + (interactive (progn (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))))) + (diredp-do-bookmark prefix 1)) + +;;;###autoload +(defun diredp-tag-this-file (tags &optional prefix) ; Bound to `T +' + "In Dired, add some tags to the file on the cursor line. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))))) + (diredp-do-tag tags prefix 1)) + +;;;###autoload +(defun diredp-untag-this-file (tags &optional prefix arg) ; Bound to `T -' + "In Dired, remove some tags from the file on the cursor line. +With a prefix arg, remove all tags from the file. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let* ((pref (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))) + (bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil pref)) + (btgs (and bmk (bmkp-get-tags bmk)))) + (unless btgs (error "File has no tags to remove")) + (list (if current-prefix-arg btgs (bmkp-read-tags-completing btgs)) + pref + current-prefix-arg)))) + (diredp-do-untag tags prefix 1)) + +;;;###autoload +(defun diredp-remove-all-tags-this-file (&optional prefix msgp) ; Bound to `T 0' + "In Dired, remove all tags from this file. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-remove-all-tags prefix 1)) + +;;;###autoload +(defun diredp-paste-add-tags-this-file (&optional prefix msgp) ; Bound to `T p', `T C-y' + "In Dired, add previously copied tags to this file. +See `diredp-paste-add-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-paste-add-tags prefix 1)) + +;;;###autoload +(defun diredp-paste-replace-tags-this-file (&optional prefix msgp) ; Bound to `T q' + "In Dired, replace tags for this file with previously copied tags. +See `diredp-paste-replace-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-paste-add-tags prefix 1)) + +;;;###autoload +(defun diredp-set-tag-value-this-file (tag value &optional prefix msgp) ; Bound to `T v' + "In Dired, Set value of TAG to VALUE for this file. +See `diredp-set-tag-value'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tag-completing) + (read (read-string "Value: ")) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-set-tag-value tag value prefix 1)) + +;;;###autoload +(defun diredp-copy-tags-this-file (&optional prefix msgp) ; Bound to `T c', `T M-w' + "In Dired, copy the tags from this file, so you can paste them to another. +See `diredp-copy-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (let ((bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil prefix))) + (and bmk (bmkp-copy-tags bmk msgp)))) + +;;;###autoload +(defun diredp-mouse-copy-tags (event) ; Not bound + "In Dired, copy the tags from this file, so you can paste them to another. +You need library `bookmark+.el' to use this command." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (diredp-copy-tags-this-file prefix 'MSG)) + (diredp-previous-line 1)) + +(when (fboundp 'describe-file) ; In `help-fns+.el' or `help+20.el'. + (defun diredp-describe-file (&optional internal-form-p) ; Bound to `C-h RET', `C-h C-RET' + "In Dired, describe this file or directory. +You need library `help-fns+.el' to use this command. +If the file has an autofile bookmark and you use library `Bookmark+', +then show also the bookmark information (tags etc.). In this case, a +prefix arg shows the internal form of the bookmark." + (interactive "P") + (describe-file (dired-get-filename nil t) internal-form-p)) + + (defun diredp-mouse-describe-file (event &optional internal-form-p) ; Not bound + "Describe the clicked file. +You need library `help-fns+.el' to use this command. +If the file has an autofile bookmark and you use library `Bookmark+', +then show also the bookmark information (tags etc.). In this case, a +prefix arg shows the internal form of the bookmark." + (interactive "e\nP") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (describe-file file internal-form-p)))) + +;; Define these even if `Bookmark+' is not loaded. +;;;###autoload +(defalias 'diredp-show-metadata 'diredp-describe-autofile) +;;;###autoload +(defun diredp-describe-autofile (&optional internal-form-p) + "Show the metadata for the file of the current line. +The file must name an autofile bookmark. The metadata is the bookmark +information. + +With a prefix argument, show the internal definition of the bookmark. + +You need library `bookmark+.el' for this command." + (interactive "P") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let ((bmk (save-match-data + (bmkp-get-autofile-bookmark (dired-get-filename nil t))))) + (unless bmk (error "Not on an autofile bookmark")) + (save-selected-window (if internal-form-p + (bmkp-describe-bookmark-internals bmk) + (bmkp-describe-bookmark bmk))))) + +(defun diredp-mouse-describe-autofile (event &optional internal-form-p) ; Not bound + "Show the metadata for the file whose name you click. +The file must name an autofile bookmark. The metadata is the bookmark +information. + +With a prefix argument, show the internal definition of the bookmark. + +You need library `bookmark+.el' for this command." + (interactive "e\nP") + (diredp-ensure-bookmark+) + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (diredp-ensure-mode) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (let ((bmk (save-match-data (bmkp-get-autofile-bookmark file)))) + (unless bmk (error "Not an autofile bookmark")) + (save-selected-window (if internal-form-p + (bmkp-describe-bookmark-internals bmk) + (bmkp-describe-bookmark bmk)))))) + +;;;###autoload +(defalias 'diredp-show-metadata-for-marked 'diredp-describe-marked-autofiles) +;;;###autoload +(defun diredp-describe-marked-autofiles (&optional internal-form-p interactivep details) + "Show metadata for the marked files. +If no file is marked, describe ALL autofiles in this directory. +With a prefix argument, show the internal (Lisp) form of the metadata. +When invoked interactively, raise an error if no files are marked. +You need library `bookmark+.el' for this command. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (list current-prefix-arg t diredp-list-file-attributes)) + (diredp-ensure-bookmark+) + (let ((help-xref-following nil)) + (help-setup-xref (list `(lambda (_buf) + (with-current-buffer ,(current-buffer) (diredp-describe-marked-autofiles))) + internal-form-p) + (if (or (> emacs-major-version 23) + (and (= emacs-major-version 23) (> emacs-minor-version 1))) + (called-interactively-p 'interactive) + (interactive-p)))) + (diredp-with-help-window "*Help*" + (let ((marked (dired-get-marked-files nil nil nil 'DISTINGUISH-ONE-MARKED interactivep))) + (unless (cdr marked) + (message "Describing ALL autofiles here (none are marked)...") + (setq marked (diredp-get-files 'IGNORE-MARKS-P nil nil nil nil details))) + (if (eq t (car marked)) + (diredp-describe-autofile internal-form-p) + (dolist (bmk (delq nil (mapcar #'bmkp-get-autofile-bookmark marked))) + (if internal-form-p + (let* ((bname (bmkp-bookmark-name-from-record bmk)) + (help-text (format "%s\n%s\n\n%s" + bname (make-string (length bname) ?-) (pp-to-string bmk)))) + (princ help-text) (terpri)) + (princ (bmkp-bookmark-description bmk)) (terpri))))))) + +;;;###autoload +(defun diredp-byte-compile-this-file () ; Bound to `b' + "In Dired, byte compile the (Lisp source) file on the cursor line." + (interactive) (dired-do-byte-compile 1)) + +;;;###autoload +(defun diredp-load-this-file () ; Not bound + "In Dired, load the file on the cursor line." + (interactive) (dired-do-load 1)) + +;;;###autoload +(defun diredp-chmod-this-file () ; Bound to `M-m' + "In Dired, change the mode of the file on the cursor line." + (interactive) (dired-do-chmod 1)) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-chgrp-this-file () ; Not bound + "In Dired, change the group of the file on the cursor line." + (interactive) (dired-do-chgrp 1))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-chown-this-file () ; Not bound + "In Dired, change the owner of the file on the cursor line." + (interactive) (dired-do-chown 1))) + +(when (fboundp 'dired-do-touch) + (defun diredp-touch-this-file () ; Not bound + "In Dired, `touch' (change the timestamp of) the file on the cursor line." + (interactive) (dired-do-touch 1))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; 1. Variable (symbol) `s' -> `blks'. +;; 2. Fixes to remove leading space from `uid' and allow `.' in `gid'. +;; 3. Cleaned up doc string and code a bit. +;; +;;;###autoload +(defun dired-mark-sexp (predicate &optional unmark-p) ; Bound to `M-(', `* (' + "Mark files for which PREDICATE returns non-nil. +With a prefix arg, unmark or unflag those files instead. + +PREDICATE is a lisp sexp that can refer to the following symbols as +variables: + + `mode' [string] file permission bits, e.g. \"-rw-r--r--\" + `nlink' [integer] number of links to file + `size' [integer] file size in bytes + `uid' [string] owner + `gid' [string] group (If the gid is not displayed by `ls', + this will still be set (to the same as uid)) + `time' [string] the time that `ls' displays, e.g. \"Feb 12 14:17\" + `name' [string] the name of the file + `sym' [string] if file is a symbolic link, the linked-to name, + else \"\" + `inode' [integer] the inode of the file (only for `ls -i' output) + `blks' [integer] the size of the file for `ls -s' output + (ususally in blocks or, with `-k', in Kbytes) +Examples: + Mark zero-length files: `(equal 0 size)' + Mark files last modified on Feb 2: `(string-match \"Feb 2\" time)' + Mark uncompiled Emacs Lisp files (`.el' file without a `.elc' file): + First, Dired just the source files: `dired *.el'. + Then, use \\[dired-mark-sexp] with this sexp: + (not (file-exists-p (concat name \"c\"))) + +There's an ambiguity when a single integer not followed by a unit +prefix precedes the file mode: It is then parsed as inode number +and not as block size (this always works for GNU coreutils ls). + +Another limitation is that the uid field is needed for the +function to work correctly. In particular, the field is not +present for some values of `ls-lisp-emulation'. + +This function operates only on the Dired buffer content. It does not +refer at all to the underlying file system. Contrast this with +`find-dired', which might be preferable for the task at hand." + ;; Using `sym' = "", instead of nil, for non-linked files avoids the trap of + ;; (string-match "foo" sym) into which a user would soon fall. + ;; Use `equal' instead of `=' in the example, as it works on integers and strings. + (interactive "xMark if (vars: inode,blks,mode,nlink,uid,gid,size,time,name,sym): \nP") + (message "%s" predicate) + (let ((dired-marker-char (if unmark-p ?\040 dired-marker-char)) + (inode nil) + (blks ()) + mode nlink uid gid size time name sym) + (diredp-mark-if + (save-excursion + (and + ;; Sets vars INODE BLKS MODE NLINK UID GID SIZE TIME NAME and SYM + ;; according to current file line. Returns `t' for success, nil if + ;; there is no file line. Upon success, these vars are set, to either + ;; nil or the appropriate value, so they need not be initialized. + ;; Moves point within the current line. + (dired-move-to-filename) + (let ((mode-len 10) ; Length of mode string. + ;; As in `dired.el', but with subexpressions \1=inode, \2=blks: + ;; GNU `ls -hs' suffixes the block count with a unit and prints it as a float; FreeBSD does neither. + ;; $$$$$$ (dired-re-inode-size "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?") + (dired-re-inode-size (if (> emacs-major-version 24) + "\\=\\s *\\([0-9]+\\s +\\)?\ +\\(?:\\([0-9]+\\(?:\\.[0-9]*\\)?[BkKMGTPEZY]?\\)? ?\\)" + "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?")) + pos) + (beginning-of-line) + (forward-char 2) + (search-forward-regexp dired-re-inode-size nil t) + ;; XXX Might be a size not followed by a unit prefix. Could set `blks' to `inode' if it were otherwise + ;; nil, with similar reasoning as for setting `gid' to `uid', but it would be even more whimsical. + (setq inode (and (match-string 1) (string-to-number (match-string 1))) + blks (and (match-string 2) (if (fboundp 'dired-x--string-to-number) + (dired-x--string-to-number (match-string 2)) ; Emacs 25+ + (string-to-number (match-string 2)))) + mode (buffer-substring (point) (+ mode-len (point)))) + (forward-char mode-len) + (unless (eq (char-after) ?\ ) (forward-char 1)) ; Skip any extended attributes marker ("." or "+"). + (setq nlink (read (current-buffer))) + ;; Karsten Wenger <kw@cis.uni-muenchen.de> fixed uid. + + ;; Another issue is that GNU `ls -n' right-justifies numerical UIDs and GIDs, while FreeBSD + ;; left-justifies them, so do not rely on a specific whitespace layout. Both of them right-justify all + ;; other numbers, though. + ;; XXX Return a number if the `uid' or `gid' seems to be numerical? + ;; $$$$$$ (setq uid (buffer-substring (+ (point) 1) (progn (forward-word 1) (point)))) + (setq uid (buffer-substring (progn (skip-chars-forward " \t") (point)) + (progn (skip-chars-forward "^ \t") (point)))) + (cond ((> emacs-major-version 24) + (dired-move-to-filename) + (save-excursion + (setq time + ;; The regexp below tries to match from the last digit of the size field through a + ;; space after the date. Also, dates may have different formats depending on file age, + ;; so the date column need not be aligned to the right. + (buffer-substring (save-excursion (skip-chars-backward " \t") (point)) + (progn (re-search-backward directory-listing-before-filename-regexp) + (skip-chars-forward "^ \t") + (1+ (point)))) + size + (dired-x--string-to-number + ;; We know that there's some kind of number before point because the regexp search + ;; above succeeded. Not worth doing an extra check for leading garbage. + (buffer-substring (point) (progn (skip-chars-backward "^ \t") (point)))) + ;; If no `gid' is displayed, `gid' will be set to `uid' but user will then not reference + ;; it anyway in PREDICATE. + gid + (buffer-substring (progn (skip-chars-backward " \t") (point)) + (progn (skip-chars-backward "^ \t") (point))))) + (setq name (buffer-substring (point) (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) (line-end-position)) + ""))) + (t + (re-search-forward + (if (< emacs-major-version 20) + "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\)" + dired-move-to-filename-regexp)) + (goto-char (match-beginning 1)) + (forward-char -1) + (setq size (string-to-number (buffer-substring (save-excursion (backward-word 1) + (setq pos (point))) + (point)))) + (goto-char pos) + (backward-word 1) + ;; if no `gid' is displayed, `gid' will be set to `uid' but user will then not reference + ;; it anyway in PREDICATE. + (setq gid (buffer-substring (save-excursion (forward-word 1) (point)) (point)) + time (buffer-substring (match-beginning 1) (1- (dired-move-to-filename))) + name (buffer-substring (point) (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) (line-end-position)) + ""))))) + ;; Vanilla Emacs uses `lexical-binding' = t, and it passes bindings to `eval' as a second arg. + ;; We use `lexical-binding' = nil, and anyway there should be no need to pass the bindings. + (eval predicate))) + (format "'%s file" predicate)))) + +(defun diredp-this-file-marked-p (&optional mark-char) + "Return non-nil if the file on this line is marked. +Optional arg MARK-CHAR is the type of mark to check. + If nil, then if the file has any mark, including `D', it is marked." + (and (dired-get-filename t t) (save-excursion + (beginning-of-line) + (if mark-char + (diredp-looking-at-p + (concat "^" (regexp-quote (char-to-string mark-char)))) + (not (diredp-looking-at-p "^ ")))))) + +(defun diredp-this-file-unmarked-p (&optional mark-char) + "Return non-nil if the file on this line is unmarked. +Optional arg MARK-CHAR is the type of mark to check. + If nil, then if the file has no mark, including `D', it is unmarked. + If non-nil, then it is unmarked for MARK-CHAR if it has no mark or + it has any mark except MARK-CHAR." + (and (dired-get-filename t t) (save-excursion + (beginning-of-line) + (if mark-char + (not (diredp-looking-at-p + (concat "^" (regexp-quote (char-to-string mark-char))))) + (diredp-looking-at-p "^ "))))) + +;;;###autoload +(defun diredp-mark-region-files (&optional unmark-p) ; Not bound + "Mark all of the files in the current region (if it is active). +With non-nil prefix arg, unmark them instead." + (interactive "P") + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char (if unmark-p ?\040 dired-marker-char))) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-unmarked-p)) "region file")))) + +;;;###autoload +(defun diredp-unmark-region-files (&optional mark-p) ; Not bound + "Unmark all of the files in the current region (if it is active). +With non-nil prefix arg, mark them instead." + (interactive "P") + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char (if mark-p dired-marker-char ?\040))) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-marked-p)) "region file")))) + +;;;###autoload +(defun diredp-flag-region-files-for-deletion () ; Not bound + "Flag all of the files in the current region (if it is active) for deletion." + (interactive) + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char dired-del-marker)) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-unmarked-p ?\D)) + "region file")))) + +;;;###autoload +(defun diredp-toggle-marks-in-region (start end) ; Not bound + "Toggle marks in the region." + (interactive "r") + (save-excursion + (save-restriction + (if (not (fboundp 'dired-toggle-marks)) + ;; Pre-Emacs 22. Use bol, eol. If details hidden, show first. + (let ((details-hidden-p (and (boundp 'dired-details-state) (eq 'hidden dired-details-state)))) + (widen) + (when details-hidden-p (dired-details-show)) + (goto-char start) + (setq start (line-beginning-position)) + (goto-char end) + (setq end (line-end-position)) + (narrow-to-region start end) + (dired-toggle-marks) + (when details-hidden-p (dired-details-hide))) + (narrow-to-region start end) + (dired-toggle-marks)))) + (when (and (get-buffer-window (current-buffer)) (fboundp 'fit-frame-if-one-window)) + (fit-frame-if-one-window))) + + +;;; Mouse 3 menu. +;;;;;;;;;;;;;;;;; + +(defvar diredp-file-line-overlay nil) + +;;;###autoload +(defun diredp-mouse-3-menu (event) ; Bound to `mouse-3' + "Dired pop-up `mouse-3' menu, for files in selection or current line." + (interactive "e") + (if (not (and (fboundp 'mouse3-dired-use-menu) (diredp-nonempty-region-p))) + ;; No `mouse3.el' or no region. + (if (diredp-nonempty-region-p) + ;; Region + (let ((reg-choice (x-popup-menu + event + (list "Files in Region" + (list "" + '("Mark" . diredp-mark-region-files) + '("Unmark" . diredp-unmark-region-files) + '("Toggle Marked/Unmarked" . + diredp-toggle-marks-in-region) + '("Flag for Deletion" . + diredp-flag-region-files-for-deletion)))))) + (when reg-choice (call-interactively reg-choice))) + ;; Single file/dir (no region). + (let ((mouse-pos (event-start event)) + ;; Do not use `save-excursion', because some commands will move point on purpose. + ;; Just save original point and return to it unless MOVEP is set to non-nil. + (opoint (point)) + (movep nil) + (inhibit-field-text-motion t) ; Just in case. + choice bol eol file/dir-name) + (with-current-buffer (window-buffer (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (setq bol (line-beginning-position) + eol (line-end-position)) + (unwind-protect + (when (setq file/dir-name (and (not (eobp)) (dired-get-filename nil t))) + (if diredp-file-line-overlay ; Don't re-create if exists. + (move-overlay diredp-file-line-overlay bol eol (current-buffer)) + (setq diredp-file-line-overlay (make-overlay bol eol)) + (overlay-put diredp-file-line-overlay 'face 'region)) + (sit-for 0) + (let ((map + (easy-menu-create-menu + "This File" + `( + ("Bookmark" :visible (featurep 'bookmark+) + ["Bookmark..." diredp-bookmark-this-file] + ["Add Tags..." diredp-tag-this-file + :visible (featurep 'bookmark+)] + ["Remove Tags..." diredp-untag-this-file + :visible (featurep 'bookmark+)] + ["Remove All Tags" diredp-remove-all-tags-this-file + :visible (featurep 'bookmark+)] + ["Copy Tags" diredp-copy-tags-this-file + :visible (featurep 'bookmark+)] + ["Paste Tags (Add)" diredp-paste-add-tags-this-file + :visible (featurep 'bookmark+)] + ["Paste Tags (Replace)" diredp-paste-replace-tags-this-file + :visible (featurep 'bookmark+)] + ["Set Tag Value..." diredp-set-tag-value-this-file + :visible (featurep 'bookmark+)] + ) + ["Describe" ',(if (if (> emacs-major-version 21) + (require 'help-fns+ nil t) + (require 'help+20 nil t)) + 'diredp-describe-file + 'diredp-describe-autofile)] ; Requires `bookmark+.el' + ;; Stuff from `Marks' menu. + ["Mark" dired-mark + :visible (not (eql (dired-file-marker file/dir-name) + dired-marker-char))] + ["Unmark" dired-unmark + :visible (dired-file-marker file/dir-name)] + ["Flag for Deletion" dired-flag-file-deletion + :visible (not (eql (dired-file-marker file/dir-name) + dired-del-marker))] + ["Delete..." diredp-delete-this-file] + "--" ; ------------------------------------------------------ + ;; Stuff from `Single' / `Multiple' menus. + ["Open" dired-find-file] + ["Open in Other Window" dired-find-file-other-window] + ["Open in Other Frame" diredp-find-file-other-frame] + ["Open Associated Windows App" dired-w32-browser + :visible (featurep 'w32-browser)] + ["Open in Windows Explorer" dired-w32explore + :visible (featurep 'w32-browser)] + ["View (Read Only)" dired-view-file] + ["--" 'ignore ; ------------------------------------------------- + :visible (or (atom (diredp-this-subdir)) ; Subdir line. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))] ; Not top. + ["Insert This Subdir" + (lambda () (interactive) + (call-interactively #'dired-maybe-insert-subdir) + (setq movep t)) + :visible (and (atom (diredp-this-subdir)) + (not (assoc (file-name-as-directory (diredp-this-subdir)) + dired-subdir-alist))) + :enable (atom (diredp-this-subdir))] + ["Go To Inserted Subdir" + (lambda () (interactive) + (call-interactively #'dired-maybe-insert-subdir) + (setq movep t)) + :visible (and (atom (diredp-this-subdir)) + (assoc (file-name-as-directory (diredp-this-subdir)) + dired-subdir-alist)) + :enable (atom (diredp-this-subdir)) + :keys "i"] + ["Remove This Inserted Subdir" dired-kill-subdir + :visible (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))] ; In subdir, not top. + ["Remove This Inserted Subdir and Lower" diredp-kill-this-tree + :visible (and (fboundp 'diredp-kill-this-tree) + (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))] ; In subdir, not top. + ["Dired This Inserted Subdir (Tear Off)" + (lambda () (interactive) (diredp-dired-this-subdir t)) + :visible (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))] ; In subdir, not top. + "--" ; ------------------------------------------------------ + ["Compare..." diredp-ediff] + ["Diff..." dired-diff] + ["Diff with Backup" dired-backup-diff] + + ["Bookmark..." diredp-bookmark-this-file + :visible (not (featurep 'bookmark+))] + "--" ; ------------------------------------------------------ + ["Rename to..." diredp-rename-this-file] + ["Capitalize" diredp-capitalize-this-file] + ["Upcase" diredp-upcase-this-file] + ["Downcase" diredp-downcase-this-file] + "--" ; ------------------------------------------------------ + ["Copy to..." diredp-copy-this-file] + ["Symlink to (Relative)..." diredp-relsymlink-this-file] + ["Symlink to..." diredp-symlink-this-file] + ["Hardlink to..." diredp-hardlink-this-file] + "--" ; ------------------------------------------------------ + ["Shell Command..." diredp-shell-command-this-file] + ["Asynchronous Shell Command..." + diredp-async-shell-command-this-file] + ["Print..." diredp-print-this-file] + ["Grep" diredp-grep-this-file] + ["Compress/Uncompress" diredp-compress-this-file] + ["Byte-Compile" diredp-byte-compile-this-file] + ["Load" diredp-load-this-file] + "--" ; ------------------------------------------------------ + ["Change Timestamp..." diredp-touch-this-file] + ["Change Mode..." diredp-chmod-this-file] + ["Change Group..." diredp-chgrp-this-file + :visible (fboundp 'diredp-chgrp-this-file)] + ["Change Owner..." diredp-chown-this-file + :visible (fboundp 'diredp-chown-this-file)])))) + (when diredp-file-line-overlay + (delete-overlay diredp-file-line-overlay)) + (setq choice (x-popup-menu event map)) + (when choice (call-interactively (lookup-key map (apply 'vector choice)))))) + (unless movep (goto-char opoint)))))) + ;; `mouse3.el' and active region. + (unless (eq mouse3-dired-function 'mouse3-dired-use-menu) + (funcall #'mouse3-dired-use-menu) + (revert-buffer)) + (let ((last-command 'mouse-save-then-kill)) (mouse-save-then-kill event)))) + + +;; REPLACE ORIGINAL in `dired.el' for Emacs 20. +;; +;; Allow `.' and `..', by using non-nil second arg to `dired-get-filename'. +;; +(when (< emacs-major-version 21) + (defun dired-find-file () ; Bound to `RET' + "In Dired, visit the file or directory named on this line." + (interactive) + (let* ((dgf-result (or (dired-get-filename nil t) (error "No file on this line"))) + (file-name (file-name-sans-versions dgf-result t))) + (if (file-exists-p file-name) + (find-file file-name) + (if (file-symlink-p file-name) + (error "File is a symlink to a nonexistent target") + (error "File no longer exists; type `g' to update Dired buffer")))))) + +;;;###autoload +(defun diredp-find-file-other-frame () ; Bound to `C-o' + "In Dired, visit this file or directory in another frame." + (interactive) + (find-file-other-frame (file-name-sans-versions (dired-get-filename nil t) t))) + +;;;###autoload +(defun diredp-mouse-find-file-other-frame (event) ; Bound to `M-mouse-2' + "In Dired, visit file or directory clicked on in another frame." + (interactive "e") + (let ((pop-up-frames t)) (dired-mouse-find-file-other-window event))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Allow `.' and `..', by using non-nil second arg to `dired-get-filename'. +;; +;;;###autoload +(defun dired-mouse-find-file-other-window (event) ; Bound to `mouse-2' + "In Dired, visit the file or directory name you click on." + (interactive "e") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (unless (stringp file) (error "No file here")) + (select-window (posn-window (event-end event))) + (find-file-other-window (file-name-sans-versions file t)))) + +;;;###autoload +(defun diredp-mouse-view-file (event) ; Not bound + "Examine this file in view mode, returning to Dired when done. +When file is a directory, show it in this buffer if it is inserted; +otherwise, display it in another buffer." + (interactive "e") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (select-window (posn-window (event-end event))) + (if (file-directory-p file) + (or (and (cdr dired-subdir-alist) (dired-goto-subdir file)) (dired file)) + (view-file file)))) ; In `view.el'. + +;;;###autoload +(defun diredp-mouse-ediff (event) ; Not bound + "Compare this file (pointed by mouse) with file FILE2 using `ediff'. +FILE2 defaults to this file as well. If you enter just a directory +name for FILE2, then this file is compared with a file of the same +name in that directory. FILE2 is the second file given to `ediff'; +this file is the first given to it." + (interactive "e") + (require 'ediff) + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (call-interactively 'diredp-ediff))) + +;;;###autoload +(defun diredp-mouse-diff (event &optional switches) ; Not bound + "Compare this file (pointed by mouse) with file FILE2 using `diff'. +FILE2 defaults to the file at the mark. This file is the first file +given to `diff'. With prefix arg, prompt for second arg SWITCHES, +which are options for `diff'." + (interactive "e") + (let ((default (and (mark t) (save-excursion (goto-char (mark t)) + (dired-get-filename t t)))) + (mouse-pos (event-start event))) + (require 'diff) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (let ((file2 (read-file-name (format "Diff %s with: %s" + (dired-get-filename t) + (if default (concat "(default " default ") ") "")) + (dired-current-directory) default t))) + (setq switches (and current-prefix-arg + (if (fboundp 'icicle-read-string-completing) ; In `icicles-fn.el' + (icicle-read-string-completing "Options for diff: " + (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")) + (lambda (c) + (diredp-string-match-p "switches" + (symbol-name c)))) + (read-string "Options for diff: " (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")))))) + (diff file2 (dired-get-filename t) switches)))) + +;;;###autoload +(defun diredp-mouse-backup-diff (event) ; Not bound + "Diff this file with its backup file or vice versa. +Use the latest backup, if there are several numerical backups. +If this file is a backup, diff it with its original. +The backup file is the first file given to `diff'. +With prefix arg, prompt for SWITCHES which are the options for `diff'." + (interactive "e") + (let ((switches (and current-prefix-arg + (if (fboundp 'icicle-read-string-completing) ; In `icicles-fn.el' + (icicle-read-string-completing "Options for diff: " + (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")) + (lambda (c) + (diredp-string-match-p "switches" + (symbol-name c)))) + (read-string "Options for diff: " (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")))))) + (mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (diff-backup (dired-get-filename) switches))) + +;;;###autoload +(defun diredp-mouse-mark (event) ; Not bound + "In Dired, mark this file. +If on a subdir headerline, mark all its files except `.' and `..'. + +Use \\[dired-unmark-all-files] to remove all marks, +and \\[dired-unmark] on a subdir to remove the marks in this subdir." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (if (and (cdr dired-subdir-alist) (dired-get-subdir)) + (save-excursion (dired-mark-subdir-files)) + (let ((buffer-read-only nil)) + (dired-repeat-over-lines 1 #'(lambda () (delete-char 1) (insert dired-marker-char))) + (diredp-previous-line 1)))) + +;;;###autoload +(defun diredp-mouse-unmark (event) ; Not bound + "In Dired, unmark this file. +If looking at a subdir, unmark all its files except `.' and `..'." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let ((dired-marker-char ?\040)) (dired-mark nil)) + (diredp-previous-line 1)) + +;;; This can be bound to [C-down-mouse-1] to give behavior similar to Windows Explorer. +;;; However, Emacs generally uses [C-down-mouse-1] for `mouse-buffer-menu'. +;;;###autoload +(defun diredp-mouse-mark/unmark (event) ; Not bound + "Mark/unmark file or directory at mouse EVENT." + (interactive "e") + (let* ((mouse-pos (event-start event)) + (inhibit-field-text-motion t) ; Just in case. + (file/dir-name (with-current-buffer (window-buffer (posn-window mouse-pos)) + (save-excursion + (goto-char (posn-point mouse-pos)) + (and (not (eobp)) (dired-get-filename nil t)))))) + ;; Return nil iff not on a file or directory name. + (and file/dir-name (cond ((dired-file-marker file/dir-name) + (diredp-mouse-unmark event) + (message "Unmarked: %s" file/dir-name)) + (t + (diredp-mouse-mark event) + (message "Marked: %s" file/dir-name)))))) + +;; This can be bound to [S-mouse-1] to give behavior similar to Windows Explorer. +;; If you do that, consider binding `diredp-mouse-mark/unmark' to `C-mouse-1'. +;; Alternatively, just bind `diredp-mouse-mark/unmark-mark-region-files' to [S-mouse-1]. +;;;###autoload +(defun diredp-mouse-mark-region-files (event) ; Bound to `S-mouse-1' + "Mark files between point and the mouse." + (interactive "e") + (call-interactively 'mouse-save-then-kill) + (diredp-mark-region-files)) + +;; This can be bound to [S-mouse-1] to give behavior similar to Windows Explorer. +;; If you don't bind `diredp-mouse-mark/unmark' to, for instance, `C-mouse-1', then +;; Consider binding this to [S-mouse-1]. +;;;###autoload +(defun diredp-mouse-mark/unmark-mark-region-files (event) ; Not bound + "Mark/unmark file or mark files in region. +If the file the cursor is on is marked, then mark all files between it + and the line clicked (included). +Otherwise (cursor's file is unmarked): + If the file clicked is marked, then unmark it. + If it is unmarked, then mark it." + (interactive "e") + (let ((mouse-pos (event-start event))) + ;; If same click same line as cursor, or cursor's line is marked, + ;; Then toggle the clicked line's mark. + ;; Else mark all files in region between point and clicked line (included). + (if (or (eq (count-lines (point-min) (posn-point mouse-pos)) + (count-lines (point-min) (point))) + (equal dired-marker-char (dired-file-marker (dired-get-filename nil t)))) + (diredp-mouse-mark/unmark event) + (call-interactively 'mouse-save-then-kill) + (diredp-mark-region-files)))) + +;;;###autoload +(defun diredp-mouse-flag-file-deletion (event) ; Not bound + "In Dired, flag this file for deletion. +If on a subdir headerline, mark all its files except `.' and `..'." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let ((dired-marker-char dired-del-marker)) (dired-mark 1)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-copy (event) ; Not bound + "In Dired, copy this file. +This normally preserves the last-modified date when copying." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'copy #'dired-copy-file (if dired-copy-preserve-time "Copy [-p]" "Copy") + 1 dired-keep-marker-copy)) + +;;;###autoload +(defun diredp-mouse-do-rename (event) ; Not bound + "In Dired, rename this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'move #'dired-rename-file "Move" 1 dired-keep-marker-rename "Rename")) + +;;;###autoload +(defun diredp-mouse-upcase (event) ; Not bound + "In Dired, rename this file to upper case." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-rename-non-directory #'upcase "Rename to uppercase:" nil)) + +;;;###autoload +(defun diredp-mouse-downcase (event) ; Not bound + "In Dired, rename this file to lower case." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-rename-non-directory #'downcase "Rename to lowercase:" nil)) + +;;;###autoload +(defun diredp-mouse-do-delete (event) ; Not bound + "In Dired, delete this file, upon confirmation." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (diredp-internal-do-deletions (dired-map-over-marks (cons (dired-get-filename) (point)) 1) + 1 + 'USE-TRASH-CAN) ; This arg is for Emacs 24+ only. + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-shell-command (event) ; Not bound + "Run a shell COMMAND on this file. +If there is output, it goes to a separate buffer. + +No automatic redisplay of Dired buffers is attempted, as there's no +telling what files the command may have changed. Type +\\[dired-do-redisplay] to redisplay. + +The shell command has the top level directory as working directory, so +output files usually are created there instead of in a subdir." + ;;Functions dired-run-shell-command and dired-shell-stuff-it do the + ;;actual work and can be redefined for customization. + (interactive "e") + (lexical-let ((mouse-pos (event-start event)) + (command (dired-read-shell-command "! on %s: " nil (dired-get-marked-files t nil)))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-bunch-files (- 10000 (length command)) + (lambda (&rest files) (dired-run-shell-command (dired-shell-stuff-it command files t 1))) + nil + (dired-get-marked-files t 1)))) + +;;;###autoload +(defun diredp-mouse-do-symlink (event) ; Not bound + "Make symbolic link to this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'symlink #'make-symbolic-link "Symlink" 1 dired-keep-marker-symlink)) + +;;;###autoload +(defun diredp-mouse-do-hardlink (event) ; Not bound + "Make hard link (alias) to this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'hardlink #'add-name-to-file "Hardlink" 1 dired-keep-marker-hardlink)) + +;;;###autoload +(defun diredp-mouse-do-print (event) ; Not bound + "Print this file. +Uses the shell command coming from variables `lpr-command' and +`lpr-switches' as default." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let* ((file (dired-get-filename)) + (command (dired-mark-read-string "Print %s with: " + (apply 'concat lpr-command " " lpr-switches) + 'print 1 (list file)))) + (dired-run-shell-command (dired-shell-stuff-it command (list file) nil)))) + +;;;###autoload +(defun diredp-mouse-do-grep (event) ; Not bound + "Run grep against this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (unless grep-command (grep-compute-defaults)) + (grep (diredp-do-grep-1 (list (dired-get-filename t))))) + +;;;###autoload +(defun diredp-mouse-do-compress (event) ; Not bound + "Compress or uncompress this file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-compress 1 'compress t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-byte-compile (event) ; Not bound + "Byte compile this file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-byte-compile 1 'byte-compile t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-load (event) ; Not bound + "Load this Emacs Lisp file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-load 1 'load t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-chmod (event) ; Not bound + "Change the mode of this file. +This calls chmod, so symbolic modes like `g+w' are allowed." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Mode" "chmod" 'chmod 1) + (diredp-previous-line 1)) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-mouse-do-chgrp (event) ; Not bound + "Change the group of this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Group" "chgrp" 'chgrp 1) + (diredp-previous-line 1))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-mouse-do-chown (event) ; Not bound + "Change the owner of this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Owner" dired-chown-program 'chown 1) + (diredp-previous-line 1))) + + +;;; Breadcrumbs + +(when (fboundp 'define-minor-mode) + + ;; Macro `define-minor-mode' is not defined in Emacs 20, so in order to be able to byte-compile + ;; this file in Emacs 20, prohibit byte-compiling of the `define-minor-mode' call. + ;; + (eval '(define-minor-mode diredp-breadcrumbs-in-header-line-mode + "Toggle the use of breadcrumbs in Dired header line. +With arg, show breadcrumbs iff arg is positive." + :init-value nil :group 'header-line :group 'Dired-Plus + (unless (derived-mode-p 'dired-mode) + (error "You must be in Dired or a mode derived from it to use this command")) + (if diredp-breadcrumbs-in-header-line-mode + (diredp-set-header-line-breadcrumbs) + (setq header-line-format (default-value 'header-line-format))))) + + (defun diredp-set-header-line-breadcrumbs () + "Show a header line with breadcrumbs to parent directories." + (let ((parent (diredp-parent-dir default-directory)) + (dirs ()) + (text "")) + (while parent + (push parent dirs) + (setq parent (diredp-parent-dir parent))) + (dolist (dir dirs) + (let* ((crumbs-map (make-sparse-keymap)) + (menu-map (make-sparse-keymap "Breadcrumbs in Header Line")) + ;; The next three are for showing the root as absolute and the rest as relative. + (rootp (diredp-root-directory-p dir)) + (parent-rootp (and (not rootp) (diredp-root-directory-p (diredp-parent-dir dir)))) + (rdir dir)) + ;; (define-key crumbs-map [header-line mouse-3] menu-map) + (unless rootp (setq rdir (file-name-nondirectory (directory-file-name dir)))) + (when dir + (setq rdir (propertize rdir + 'local-map (progn (define-key crumbs-map [header-line mouse-1] + `(lambda () (interactive) + (dired ,dir dired-actual-switches))) + (define-key crumbs-map [header-line mouse-2] + `(lambda () (interactive) + (dired-other-window ,dir dired-actual-switches))) + crumbs-map) + 'mouse-face 'mode-line-highlight + ;;'help-echo "mouse-1: Dired; mouse-2: Dired in other window; mouse-3: Menu")) + 'help-echo "mouse-1: Dired; mouse-2: Dired in other window")) + (setq text (concat text (if (or rootp parent-rootp) " " " / ") rdir))))) + (make-local-variable 'header-line-format) + (setq header-line-format text))) + + ;; Users can do this. + ;; + ;; (add-hook 'dired-before-readin-hook 'diredp-breadcrumbs-in-header-line-mode) + + ) + + +;;; `Dired+' Help + +;;;###autoload +(defun diredp-describe-mode (&optional buffer) + "Describe Dired mode, including Dired+ features. +This is `describe-mode' plus a description of Dired+ features. +For just the latter, use \\<dired-mode-map>`\\[diredp-dired-plus-help]'." + (interactive "@") + (unless (derived-mode-p 'dired-mode) + (error "Use `diredp-dired-plus-help' if you want information about Dired+")) + (with-current-buffer (or buffer (current-buffer)) (describe-mode)) + (with-current-buffer (get-buffer-create "*Help*") + (save-excursion + (goto-char (point-min)) + (diredp-dired-plus-help-link) + (let ((buffer-read-only nil)) (insert "\n")) + (when (re-search-forward "Keybindings:\nkey\\s-+binding\n---\\s-+-------" nil t) + (goto-char (match-beginning 0)) + (let ((buffer-read-only nil)) + (insert "\f\n") + (diredp-dired-plus-description+links) + (insert "\f\n")))))) + +;;;###autoload +(defun diredp-dired-plus-help () + "Describe Dired+." + (interactive "@") + (diredp-with-help-window "*Help*" (diredp-dired-plus-description+links))) + +(defun diredp-dired-plus-description+links () + "Insert Dired+ help text in `*Help*'." + (with-current-buffer (get-buffer-create "*Help*") + (let ((buffer-read-only nil)) + (save-restriction + (narrow-to-region (point) (point)) + (diredp-dired-plus-help-link) + (insert (diredp-dired-plus-description)) + (goto-char (point-max)) + (insert "\n") + (diredp-dired-plus-help-link))))) + +(when (and (> emacs-major-version 21) + (require 'help-mode nil t) + (get 'help-xref 'button-category-symbol)) ; `button.el' + (define-button-type 'diredp-help-button + :supertype 'help-xref + 'help-function #'(lambda () (browse-url "https://www.emacswiki.org/emacs/DiredPlus")) + 'help-echo + (purecopy "mouse-2, RET: Dired+ documentation on the Emacs Wiki (requires \ +Internet access)"))) + +(defun diredp-dired-plus-help-link () + "Add Web link for Dired+ help, and reminder about sending bug report." + ;; Don't bother to do this for Emacs 21.3. Its `help-insert-xref-button' is different. + (when (and (> emacs-major-version 21) + (require 'help-mode nil t) + (fboundp 'help-insert-xref-button)) ; `help-mode.el'. + (let ((buffer-read-only nil)) + (help-insert-xref-button "[Dired+ Help on the Web]" 'diredp-help-button) + (insert (substitute-command-keys + "\t\tSend a Dired+ bug report:\n\t\t\t\t\t`\\[diredp-send-bug-report]'\n"))))) + +(defun diredp-dired-plus-description () + "Dired+ description." + (substitute-command-keys + (concat + "\\<dired-mode-map>\ + Dired+ Features + --------------- + +To see or customize the Dired+ options or faces, use +`M-x customize-option diredp TAB' or `M-x customize-face diredp TAB'. + +Most keys listed here are in addition to those for vanilla Dired. + +Menus +----- + +Many Dired+ actions are available from the menu-bar menus and the +`mouse-3' context menu. This may include commands shown here as not +being bound to keys (i.e., listed as `M-x ...'). + +General Here +------------ + +" + (and (fboundp 'diredp-w32-drives) + " \\[diredp-w32-drives]\t\t- Go up to a list of MS Windows drives +") + (and (fboundp 'dired-hide-details-mode) + " \\[dired-hide-details-mode]\t\t- Hide/show details +") + + " \\[revert-buffer]\t\t- Refresh (sync and show all) + \\[diredp-toggle-find-file-reuse-dir]\t- Toggle reusing directories +" + " \\[diredp-marked-other-window]\t\t- Open Dired on marked files here + \\[diredp-dired-inserted-subdirs]\t\t- Dired separately each subdir inserted here +" + (and (featurep 'bookmark+) + " \\[diredp-highlight-autofiles-mode]\t- Toggle autofile highlighting + +") + + "General Globally +---------------- + +\\<global-map>\ + \\[diredp-add-to-dired-buffer]\t- Add files to a Dired buffer + \\[diredp-fileset]\t- Open Dired on files in a fileset + \\[diredp-dired-recent-dirs]\t- Open Dired on recently used dirs + \\[diredp-dired-union]\t- Create union of some Dired buffers + \\[diredp-dired-for-files]\t- Open Dired on files located anywhere +\\<dired-mode-map>\ + +Mouse +----- + + \\[diredp-mouse-3-menu]\t- Context-sensitive menu +" + + (and (where-is-internal 'diredp-mouse-describe-file dired-mode-map) + " \\[diredp-mouse-describe-file]\t- Describe file +") + + (and (where-is-internal 'diredp-mouse-describe-autofile dired-mode-map) + " \\[diredp-mouse-describe-autofile]\t- Describe autofile +") + + " \\[diredp-mouse-mark-region-files]\t\t- Mark all in region +" + + (and (fboundp 'dired-mouse-w32-browser) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser dired-mode-map) + " \\[dired-mouse-w32-browser]\t\t- MS Windows `Open' action +") + (and (fboundp 'dired-mouse-w32-browser-reuse-dir-buffer) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + " \\[dired-mouse-w32-browser-reuse-dir-buffer]\t- MS Windows `Open' action +") + + (and (where-is-internal 'dired-mouse-find-file dired-mode-map) + " \\[dired-mouse-find-file]\t- Open in this window +") + (and (where-is-internal 'diredp-mouse-find-file-reuse-dir-buffer dired-mode-map) + " \\[diredp-mouse-find-file-reuse-dir-buffer]\t- Open in this window +") + + (and (where-is-internal 'dired-mouse-find-file-other-window dired-mode-map) + " \\[dired-mouse-find-file-other-window]\t\t- Open in another window +") + + " \\[diredp-mouse-find-file-other-frame]\t\t- Open in another frame +" + + " +Marking +------- + + \\[dired-mark]\t\t- Mark this file/dir + \\[dired-unmark]\t\t- Unmark this file/dir + \\[dired-toggle-marks]\t\t- Toggle marked/unmarked + \\[dired-mark-sexp]\t\t- Mark all satisfying a predicate + \\[dired-unmark-all-marks]\t\t- Unmark all + \\[diredp-mark/unmark-extension]\t\t- Mark/unmark all that have a given extension +" + + (and (fboundp 'dired-mark-omitted) ; In `dired-x.el' Emacs 22+. + " \\[dired-mark-omitted]\t\t- Mark omitted +") + + " \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags +" + + " +Current file/subdir (current line) +---------------------------------- + + \\[diredp-describe-file]\t- Describe + \\[dired-find-file]\t\t- Open +" + (and (fboundp 'dired-mouse-w32-browser) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser dired-mode-map) + " \\[dired-mouse-w32-browser]\t- MS Windows `Open' action + \\[dired-w32explore]\t- MS Windows Explorer +") + + " \\[diredp-byte-compile-this-file]\t\t- Byte-compile + \\[diredp-compress-this-file]\t\t- Compress/uncompress + \\[diredp-print-this-file]\t\t- Print + \\[diredp-relsymlink-this-file]\t\t- Create relative symlink + \\[diredp-delete-this-file]\t\t- Delete (with confirmation) + \\[diredp-rename-this-file]\t\t- Rename + \\[diredp-capitalize-this-file]\t\t- Capitalize (rename) + \\[diredp-upcase-this-file]\t\t- Rename to uppercase + \\[diredp-downcase-this-file]\t\t- Rename to lowercase + \\[diredp-ediff]\t\t- Ediff + \\[diredp-bookmark-this-file]\t\t- Bookmark +" + (and (featurep 'bookmark+) + " \\[diredp-tag-this-file]\t\t- Add some tags to this file/dir + \\[diredp-untag-this-file]\t\t- Remove some tags from this file/dir + \\[diredp-remove-all-tags-this-file]\t\t- Remove all tags from this file/dir + \\[diredp-copy-tags-this-file]\t\t- Copy the tags from this file/dir + \\[diredp-paste-add-tags-this-file]\t\t- Paste (add) copied tags to this file/dir + \\[diredp-paste-replace-tags-this-file]\t\t- Paste (replace) tags for this file/dir + \\[diredp-set-tag-value-this-file]\t\t- Set a tag value for this file/dir +") + + (and (fboundp 'dired-mouse-w32-browser-reuse-dir-buffer) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + " \\[dired-mouse-w32-browser-reuse-dir-buffer]\t- MS Windows `Open' action + \\[dired-w32explore]\t- MS Windows Explorer +") + + " +Marked (or next prefix arg) files & subdirs here +------------------------------------------------ +" + (and (fboundp 'dired-multiple-w32-browser) ; In `w32-browser.el'. + " + \\[dired-multiple-w32-browser]\t- MS Windows `Open' action +") + + + " \\[diredp-list-marked]\t\t- List marked files and directories + \\[diredp-insert-subdirs]\t\t- Insert marked subdirectories + \\[dired-copy-filename-as-kill]\t\t- Copy names for pasting + M-o \\[dired-copy-filename-as-kill]\t\t- Copy absolute names for pasting + \\[diredp-yank-files]\t\t- Paste files whose absolute names you copied + \\[dired-do-find-marked-files]\t\t- Visit + \\[dired-do-copy]\t\t- Copy + \\[dired-do-rename]\t\t- Rename/move + \\[diredp-do-grep]\t\t- Run `grep' + \\[dired-do-search]\t\t- Search +" + (and (fboundp 'dired-do-find-regexp) ; Emacs 25+ + " \\[dired-do-find-regexp]\t\t- Search using `find' +") + + (if (fboundp 'dired-do-query-replace-regexp) ; Emacs 22+ + " \\[dired-do-query-replace-regexp]\t\t- Query-replace +" + " \\[dired-do-query-replace]\t\t- Query-replace +") + + (and (fboundp 'dired-do-find-regexp-and-replace) + " \\[dired-do-find-regexp-and-replace]\t\t- Query-replace using `find' +") + + (and (fboundp 'dired-do-isearch) + " \\[dired-do-isearch]\t- Isearch + \\[dired-do-isearch-regexp]\t- Regexp isearch +") + + (and (fboundp 'dired-do-async-shell-command) + " \\[dired-do-async-shell-command]\t\t- Run shell command asynchronously +") + + " \\[dired-do-shell-command]\t\t- Run shell command + \\[diredp-marked-other-window]\t\t- Dired + \\[dired-do-compress]\t\t- Compress + \\[dired-do-byte-compile]\t\t- Byte-compile + \\[dired-do-load]\t\t- Load (Emacs Lisp) + \\[diredp-do-apply-function]\t\t- Apply Lisp function + \\[diredp-do-emacs-command]\t\t- Invoke Emacs command +" + (and (fboundp 'diredp-read-expression) ; Emacs 22+ + " \\[diredp-do-lisp-sexp]\t\t- Evaluate Lisp sexp +") + + " \\[diredp-omit-marked]\t- Omit + \\[diredp-omit-unmarked]\t- Omit unmarked +" + + (and (featurep 'bookmark+) + " + \\[diredp-do-tag]\t\t- Add some tags to marked + \\[diredp-do-untag]\t\t- Remove some tags from marked + \\[diredp-do-remove-all-tags]\t\t- Remove all tags from marked + \\[diredp-do-paste-add-tags]\t- Paste (add) copied tags to marked + \\[diredp-do-paste-replace-tags]\t\t- Paste (replace) tags for marked + \\[diredp-do-set-tag-value]\t\t- Set a tag value for marked + \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags") + + " + + \\[diredp-do-bookmark]\t\t- Bookmark +" + + (and (featurep 'bookmark+) + " \\[diredp-set-bookmark-file-bookmark-for-marked]\t\t- \ +Bookmark and create bookmark-file bookmark + \\[diredp-do-bookmark-in-bookmark-file]\t- Bookmark in specific bookmark file +") + + " +Here and below (in marked subdirs) +---------------------------------- +" + (and (fboundp 'dired-multiple-w32-browser) ; In `w32-browser.el'. + " + \\[diredp-multiple-w32-browser-recursive]\t- MS Windows `Open' action +") + + " \\[diredp-list-marked-recursive]\t\t- List marked files and directories + \\[diredp-insert-subdirs-recursive]\t\t- Insert marked subdirectories + \\[diredp-copy-filename-as-kill-recursive]\t\t- Copy names for pasting + \\[diredp-do-find-marked-files-recursive]\t\t\t- Visit + \\[diredp-do-print-recursive]\t\t\t- Print + \\[diredp-do-copy-recursive]\t\t\t- Copy + \\[diredp-do-move-recursive]\t\t\t- Move + \\[diredp-do-touch-recursive]\t\t- Touch (update timestamp) + \\[diredp-do-chmod-recursive]\t\t\t- Change mode + + \\[diredp-do-symlink-recursive]\t\t\t- Add symbolic links + \\[diredp-do-relsymlink-recursive]\t\t\t- Add relative symbolic links + \\[diredp-do-hardlink-recursive]\t\t\t- Add hard links + + \\[diredp-capitalize-recursive]\t\t- Capitalize + \\[diredp-downcase-recursive]\t\t- Downcase + \\[diredp-upcase-recursive]\t\t- Upcase +" + (and (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + " + \\[diredp-do-encrypt-recursive]\t\t- Encrypt + \\[diredp-do-decrypt-recursive]\t\t- Decrypt + \\[diredp-do-sign-recursive]\t\t- Sign + \\[diredp-do-verify-recursive]\t\t- Verify +") + + " + \\[diredp-do-grep-recursive]\t\t- `grep' + \\[diredp-do-search-recursive]\t\t\t- Search + \\[diredp-do-query-replace-regexp-recursive]\t\t\t- Query-replace + \\[diredp-do-isearch-recursive]\t\t- Isearch + \\[diredp-do-isearch-regexp-recursive]\t- Regexp isearch +" + (and (fboundp 'diredp-do-async-shell-command-recursive) ; Emacs 23+ + " + \\[diredp-do-async-shell-command-recursive]\t\t\t- Run shell command asynchronously +") + + " \\[diredp-do-shell-command-recursive]\t\t\t- Run shell command + \\[diredp-do-apply-function-recursive]\t\t\t- Apply Lisp function + + \\[diredp-marked-recursive-other-window]\t\t- Dired + \\[diredp-list-marked-recursive]\t\t- List + + \\[diredp-image-dired-comment-files-recursive]\t\t- Add image comment + \\[diredp-image-dired-display-thumbs-recursive]\t\t- Show thumbnail images + \\[diredp-image-dired-tag-files-recursive]\t\t- Tag images + \\[diredp-image-dired-delete-tag-recursive]\t\t- Delete image tags + + \\[diredp-do-bookmark-recursive]\t\t- Bookmark +" + (and (featurep 'bookmark+) + " \\[diredp-do-bookmark-in-bookmark-file-recursive]\t\t- Bookmark in bookmark file + \\[diredp-set-bookmark-file-bookmark-for-marked-recursive]\t\t- Create bookmark-file bookmark +") + + " + \\[diredp-mark-directories-recursive]\t\t- Mark directories + \\[diredp-mark-executables-recursive]\t\t- Mark executables + \\[diredp-mark-symlinks-recursive]\t\t- Mark symbolic links + \\[diredp-mark-files-containing-regexp-recursive]\t\t- Mark content regexp matches + \\[diredp-mark-files-regexp-recursive]\t\t- Mark filename regexp matches +" + (and (featurep 'bookmark+) + " \\[diredp-mark-autofiles-recursive]\t\t- Mark autofiles +") + " \\[diredp-flag-auto-save-files-recursive]\t\t\t- Flag auto-save + \\[diredp-do-delete-recursive]\t\t\t- Delete marked (not flagged) + \\[diredp-change-marks-recursive]\t\t- Change marks + \\[diredp-unmark-all-files-recursive]\t\t- Remove a given mark + \\[diredp-unmark-all-marks-recursive]\t\t\t- Remove all marks +" + (and (featurep 'bookmark+) +" + +Tagging +------- + + \\[diredp-tag-this-file]\t\t- Add some tags to this file/dir + \\[diredp-untag-this-file]\t\t- Remove some tags from this file/dir + \\[diredp-remove-all-tags-this-file]\t\t- Remove all tags from this file/dir + \\[diredp-copy-tags-this-file]\t\t- Copy the tags from this file/dir + \\[diredp-paste-add-tags-this-file]\t\t- Paste (add) copied tags to this file/dir + \\[diredp-paste-replace-tags-this-file]\t\t- Paste (replace) tags for this file/dir + \\[diredp-set-tag-value-this-file]\t\t- Set a tag value for this file/dir + \\[diredp-do-tag]\t\t- Add some tags to marked + \\[diredp-do-untag]\t\t- Remove some tags from marked + \\[diredp-do-remove-all-tags]\t\t- Remove all tags from marked + \\[diredp-do-paste-add-tags]\t- Paste (add) copied tags to marked + \\[diredp-do-paste-replace-tags]\t\t- Paste (replace) tags for marked + \\[diredp-do-set-tag-value]\t\t- Set a tag value for marked + \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags +") + + " +Bookmarking +----------- + + \\[diredp-bookmark-this-file]\t\t- Bookmark this file/dir + \\[diredp-do-bookmark]\t\t- Bookmark marked" + + (and (featurep 'bookmark+) + " + \\[diredp-set-bookmark-file-bookmark-for-marked]\t\t- \ +Bookmark marked and create bookmark-file bookmark + \\[diredp-do-bookmark-in-bookmark-file]\t- Bookmark marked, in specific bookmark file +") + + " \\[diredp-do-bookmark-recursive]\t- Bookmark marked, here and below +" + (and (featurep 'bookmark+) + " \\[diredp-do-bookmark-in-bookmark-file-recursive]\t- \ +Bookmark marked, here and below, in specific file + \\[diredp-set-bookmark-file-bookmark-for-marked-recursive]\t- \ +Set bookmark-file bookmark for marked here and below +") + + ))) + +(when (> emacs-major-version 21) + (defun diredp-nb-marked-in-mode-name () + "Show number of marked, flagged, and current-list lines in mode-line. +\(Flagged means flagged for deletion.) +If the current line is marked/flagged and there are others +marked/flagged after it then show `N/M', where `N' is the number +marked/flagged through the current line and `M' is the total number +marked/flagged. + +If the current line is for a file then show `L/T', where `L' is the +line number in the current listing and `T' is the number of files in +that listing. If option `diredp-count-.-and-..-flag' is non-nil then +count also `.' and `..'. + +Also abbreviate `mode-name', using \"Dired/\" instead of \"Dired by\"." + (let ((mname (format-mode-line mode-name))) + ;; Property `dired+-mode-name' indicates whether `mode-name' has been changed. + (unless (get-text-property 0 'dired+-mode-name mname) + (save-match-data + (setq mode-name + `(,(propertize (if (string-match "^[dD]ired \\(by \\)?\\(.*\\)" mname) + (format "Dired/%s" (match-string 2 mname)) + mname) + 'dired+-mode-name t) + (:eval (let* ((dired-marker-char (if (eq ?D dired-marker-char) + ?* ; `dired-do-flagged-delete' binds it. + dired-marker-char)) + (marked-regexp (dired-marker-regexp)) + (nb-marked (count-matches marked-regexp + (point-min) (point-max)))) + (if (not (> nb-marked 0)) + "" + (propertize + (format " %s%d%c" + (save-excursion + (forward-line 0) + (if (diredp-looking-at-p (concat marked-regexp ".*")) + (format "%d/" (1+ (count-matches + marked-regexp + (point-min) (point)))) + "")) + nb-marked dired-marker-char) + 'face 'diredp-mode-line-marked 'dired+-mode-name t)))) + (:eval (let* ((flagged-regexp (let ((dired-marker-char dired-del-marker)) + (dired-marker-regexp))) + (nb-flagged (count-matches flagged-regexp + (point-min) (point-max)))) + (if (not (> nb-flagged 0)) + "" + (propertize + (format " %s%dD" + (save-excursion + (forward-line 0) + (if (diredp-looking-at-p (concat flagged-regexp ".*")) + (format "%d/" (1+ (count-matches + flagged-regexp + (point-min) (point)))) + "")) + nb-flagged) + 'face 'diredp-mode-line-flagged)))) + (:eval (save-excursion + (let ((this 0) + (total 0) + (o-pt (line-beginning-position)) + (e-pt (or (condition-case nil + (let ((diredp-wrap-around-flag nil)) + (save-excursion + (diredp-next-subdir 1) + (line-beginning-position))) + (error nil)) + (save-excursion (goto-char (point-max)) (line-beginning-position))))) + (when dired-subdir-alist (dired-goto-subdir (dired-current-directory))) + (while (and (<= (point) e-pt) + (< (point) (point-max))) ; Hack to work around Emacs display-engine bug. + (when (condition-case nil + (dired-get-filename nil diredp-count-.-and-..-flag) + (error nil)) + (when (<= (line-beginning-position) o-pt) (setq this (1+ this))) + (setq total (1+ total))) + (forward-line 1)) + (if (not (> this 0)) (format " %d" total) (format " %d/%d" this total))))))))))) + + (add-hook 'dired-after-readin-hook 'diredp-nb-marked-in-mode-name) + ;; This one is needed for `find-dired', because it does not call `dired-readin'. + (add-hook 'dired-mode-hook 'diredp-nb-marked-in-mode-name)) + +;;;###autoload +(defun diredp-send-bug-report () + "Send a bug report about a Dired+ problem." + (interactive) + (browse-url (format (concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\ +Dired+ bug: \ +&body=Describe bug below, using a precise recipe that starts with `emacs -Q' or `emacs -q'. \ +File `dired+.el' has a header `Update #' that you can use to identify it.\ +%%0A%%0AEmacs version: %s.") + (emacs-version)))) + +(defun diredp-visit-ignore-regexp () ; Taken from `image-file-name-regexp'. + "Return a regular expression matching file names to skip. +This is used by `dired-visit-(next|previous)'." + (let ((exts-regexp (and diredp-visit-ignore-extensions + (concat "\\." (regexp-opt (nconc (mapcar #'upcase diredp-visit-ignore-extensions) + diredp-visit-ignore-extensions) + t) + "\\'")))) + (if diredp-visit-ignore-regexps + (mapconcat #'identity (if exts-regexp + (cons exts-regexp diredp-visit-ignore-regexps) + diredp-visit-ignore-regexps) + "\\|") + exts-regexp))) + +;;;###autoload +(defun diredp-visit-next-file (&optional arg) ; Bound to `C-down' + "Move down a line and visit its file in another window. +With numeric prefix arg N, move down N-1 lines first. + +After moving N lines, skip any lines with file names that match either +`diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps'. + +Kill the last buffer visited by a `dired-visit-*' command." + (interactive "p") + (dired-next-line arg) + (while (diredp-string-match-p (diredp-visit-ignore-regexp) (dired-get-file-for-visit)) + (dired-next-line 1)) + (diredp-visit-this-file)) + +;;;###autoload +(defun diredp-visit-previous-file (&optional arg) ; Bound to `C-up' + "Move up a line and visit its file in another window. +With numeric prefix arg N, move up N-1 lines first. + +After moving N lines, skip any lines with file names that match either +`diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps'. + +Kill the last buffer visited by a `dired-visit-*' command." + (interactive "p") + (dired-previous-line arg) + (while (diredp-string-match-p (diredp-visit-ignore-regexp) (dired-get-file-for-visit)) + (dired-previous-line 1)) + (diredp-visit-this-file)) + +;;;###autoload +(defun diredp-visit-this-file () ; Bound to `e' (replaces `dired-find-file' binding) + "View the file on this line in another window in the same frame. +If it was not already shown there then kill the previous buffer +visited by a `dired-visit-*' command. + +If it was already shown there, and if it and Dired are the only +windows there, then delete its window (toggle : show/hide the file)." + (interactive) + (let ((file (dired-get-file-for-visit)) + (obuf (current-buffer)) + (shown nil) + fwin) + (unless (or (and (fboundp 'window-parent) (window-parent)) + (not (one-window-p 'NOMINI))) + (split-window)) + (save-selected-window + (other-window 1) + (setq fwin (selected-window)) + (unless (or (setq shown (or (equal (current-buffer) (get-file-buffer file)) + (memq (current-buffer) (dired-buffers-for-dir file)))) + (equal obuf (current-buffer))) + (kill-buffer (current-buffer)))) + (if shown + (when (= 2 (count-windows 'NOMINI)) (delete-window fwin)) + (set-window-buffer fwin (find-file-noselect file))))) + +;;; Key Bindings. + + +;; Menu Bar. +;; New order is (left -> right): +;; +;; Dir Regexp Mark Multiple Single + +;; Get rid of menu bar predefined in `dired.el'. +(define-key dired-mode-map [menu-bar] nil) +;; Get rid of Edit menu bar menu to save space. +(define-key dired-mode-map [menu-bar edit] 'undefined) + + +;; `Single' menu. +;; +;; REPLACE ORIGINAL `Immediate' menu in `dired.el'. +;; +(defvar diredp-menu-bar-single-menu (make-sparse-keymap "Single")) +(define-key dired-mode-map [menu-bar immediate] (cons "Single" diredp-menu-bar-single-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-immediate-menu 'diredp-menu-bar-single-menu)) +(make-obsolete-variable 'diredp-menu-bar-immediate-menu 'diredp-menu-bar-single-menu) ; 2017-04-09 + +(if (fboundp 'diredp-describe-file) + (define-key diredp-menu-bar-single-menu [diredp-describe-file] + '(menu-item "Describe" diredp-describe-file + :help "Describe the file or directory at cursor")) + (define-key diredp-menu-bar-single-menu [diredp-describe-autofile] + '(menu-item "Describe" diredp-describe-autofile + :help "Describe the autofile at cursor" + :enable (featurep 'bookmark+)))) +(define-key diredp-menu-bar-single-menu [separator-describe] '("--")) ; --------------------- + +(when (fboundp 'diredp-chown-this-file) + (define-key diredp-menu-bar-single-menu [chown] + '(menu-item "Change Owner..." diredp-chown-this-file + :help "Change the owner of file at cursor"))) +(when (fboundp 'diredp-chgrp-this-file) + (define-key diredp-menu-bar-single-menu [chgrp] + '(menu-item "Change Group..." diredp-chgrp-this-file + :help "Change the group of file at cursor"))) +(define-key diredp-menu-bar-single-menu [chmod] + '(menu-item "Change Mode..." diredp-chmod-this-file + :help "Change mode (attributes) of file at cursor")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-menu-bar-single-menu [touch] + '(menu-item "Change Timestamp (`touch')..." diredp-touch-this-file + :help "Change the timestamp of file at cursor, using `touch'"))) +(define-key diredp-menu-bar-single-menu [separator-change] '("--")) ; ----------------------- + +(define-key diredp-menu-bar-single-menu [print] + '(menu-item "Print..." diredp-print-this-file + :help "Print file at cursor, supplying print command")) +(define-key diredp-menu-bar-single-menu [grep] + '(menu-item "Grep..." diredp-grep-this-file :help "Grep file at cursor")) +(define-key diredp-menu-bar-single-menu [compress] + '(menu-item "Compress/Uncompress" diredp-compress-this-file + :help "Compress/uncompress file at cursor")) +(define-key diredp-menu-bar-single-menu [command] + '(menu-item "Shell Command..." diredp-shell-command-this-file + :help "Run a shell command on file at cursor")) +(define-key diredp-menu-bar-single-menu [diredp-async-shell-command-this-file] + '(menu-item "Asynchronous Shell Command..." diredp-async-shell-command-this-file + :help "Run a shell command asynchronously on file at cursor")) +(define-key diredp-menu-bar-single-menu [compile] + '(menu-item "Byte Compile" diredp-byte-compile-this-file + :help "Byte-compile this Emacs Lisp file")) +(define-key diredp-menu-bar-single-menu [load] + '(menu-item "Load" diredp-load-this-file + :help "Load this Emacs Lisp file")) + +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key diredp-menu-bar-single-menu [mkhtml-dired-files] + '(menu-item "Create HTML" mkhtml-dired-files + :help "Create an HTML file corresponding to file at cursor"))) +(define-key diredp-menu-bar-single-menu [separator-misc] '("--")) ; ------------------------- + +(define-key diredp-menu-bar-single-menu [delete] + '(menu-item "Delete" diredp-delete-this-file :help "Delete file at cursor")) +(define-key diredp-menu-bar-single-menu [separator-delete] '("--")) ; ----------------------- + +(define-key diredp-menu-bar-single-menu [backup-diff] + '(menu-item "Diff with Backup" dired-backup-diff + :help "Diff file at cursor with its latest backup")) +(define-key diredp-menu-bar-single-menu [diff] + '(menu-item "Diff..." dired-diff + :help "Compare file at cursor with another file using `diff'")) +(define-key diredp-menu-bar-single-menu [ediff] + '(menu-item "Compare..." diredp-ediff :help "Compare file at cursor with another file")) +(define-key diredp-menu-bar-single-menu [separator-diff] '("--")) ; ------------------------- + +(define-key diredp-menu-bar-single-menu [diredp-kill-this-tree] + '(menu-item "Remove This Inserted Subdir and Lower" diredp-kill-this-tree + :visible (and (fboundp 'diredp-kill-this-tree) + (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))))) ; In subdir, not top. +(define-key diredp-menu-bar-single-menu [dired-kill-subdir] + '(menu-item "Remove This Inserted Subdir" dired-kill-subdir + :visible (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))) ; In subdir, not top. +(define-key diredp-menu-bar-single-menu [diredp-dired-this-subdir] + '(menu-item "Dired This Inserted Subdir (Tear Off)" + (lambda () (interactive) (diredp-dired-this-subdir t)) + :visible (and (cdr dired-subdir-alist) ; First is current dir. Must have at least one more. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))) ; Must be sub, not top. + :help "Open Dired for subdir at or above point, tearing it off if inserted")) +(define-key diredp-menu-bar-single-menu [insert-subdir] + '(menu-item "Insert This Subdir" dired-maybe-insert-subdir + :visible (and (atom (diredp-this-subdir)) + (not (assoc (file-name-as-directory (diredp-this-subdir)) dired-subdir-alist))) + :enable (atom (diredp-this-subdir)) + :help "Insert a listing of this subdirectory")) +(define-key diredp-menu-bar-single-menu [goto-subdir] + '(menu-item "Go To Inserted Subdir" dired-maybe-insert-subdir + :visible (and (atom (diredp-this-subdir)) + (assoc (file-name-as-directory (diredp-this-subdir)) dired-subdir-alist)) + :enable (atom (diredp-this-subdir)) + :help "Go to the inserted listing of this subdirectory")) +(define-key diredp-menu-bar-single-menu [separator-subdir] '("--" ; ------------------------ + :visible (or (atom (diredp-this-subdir)) ; Subdir line. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))))) ; Not top. + +(define-key diredp-menu-bar-single-menu [view] + '(menu-item "View (Read Only)" dired-view-file + :help "Examine file at cursor in read-only mode")) +(define-key diredp-menu-bar-single-menu [display] + '(menu-item "Display in Other Window" dired-display-file + :help "Display file at cursor in a different window")) + + +;; `Single' > `Open' menu. +;; +(defvar diredp-single-open-menu (make-sparse-keymap "Rename") + "`Open' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-open] (cons "Open" diredp-single-open-menu)) + +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(progn + (define-key diredp-single-open-menu [dired-w32-browser] + '(menu-item "Open Associated Windows App" dired-w32-browser + :help "Open file using the Windows app associated with its file type")) + (define-key diredp-single-open-menu [dired-w32explore] + '(menu-item "Open in Windows Explorer" dired-w32explore + :help "Open file in Windows Explorer")))) +(define-key diredp-single-open-menu [find-file-other-frame] + '(menu-item "Open in Other Frame" diredp-find-file-other-frame + :help "Edit file at cursor in a different frame")) +(define-key diredp-single-open-menu [find-file-other-window] + '(menu-item "Open in Other Window" dired-find-file-other-window + :help "Edit file at cursor in a different window")) +(define-key diredp-single-open-menu [find-file] + '(menu-item "Open" dired-find-file :help "Edit file at cursor")) + + +;; `Single' > `Rename' menu. +;; +(defvar diredp-single-rename-menu (make-sparse-keymap "Rename") + "`Rename' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-case] (cons "Rename" diredp-single-rename-menu)) + +(define-key diredp-single-rename-menu [single-rename-capitalize] + '(menu-item "Capitalize" diredp-capitalize-this-file + :help "Capitalize (initial caps) name of file at cursor")) +(define-key diredp-single-rename-menu [single-rename-downcase] + '(menu-item "Downcase" diredp-downcase-this-file + ;; When running on plain MS-DOS, there is only one letter-case for file names. + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename file at cursor to a lower-case name")) +(define-key diredp-single-rename-menu [single-rename-upcase] + '(menu-item "Upcase" diredp-upcase-this-file + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename file at cursor to an upper-case name")) + + +;; `Single' > `Move / Copy / Link' menu. +;; +(defvar diredp-single-move-copy-link-menu (make-sparse-keymap "Move / Copy / Link") + "`Move / Copy / Link' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-move-copy-link] + (cons "Move / Copy / Link" diredp-single-move-copy-link-menu)) + +(define-key diredp-single-move-copy-link-menu [single-hardlink] + '(menu-item "Hardlink to..." diredp-hardlink-this-file + :help "Make hard links for current or marked files")) +(define-key diredp-single-move-copy-link-menu [single-symlink] + '(menu-item "Symlink to (Absolute)..." diredp-symlink-this-file + :help "Make absolute symbolic link for file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-relsymlink] + '(menu-item "Symlink to (Relative)..." diredp-relsymlink-this-file + :help "Make relative symbolic link for file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-copy] + '(menu-item "Copy to..." diredp-copy-this-file :help "Copy file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-rename] + '(menu-item "Move to..." diredp-rename-this-file + :help "Rename file at cursor, or move it to a different directory")) + + +;; `Single' > `Image' menu. +;; +(defvar diredp-single-image-menu (make-sparse-keymap "Image")) +(defalias 'diredp-single-image-menu diredp-single-image-menu) +(define-key diredp-menu-bar-single-menu [image] + '(menu-item "Image" diredp-single-image-menu + :enable (let ((img-file (diredp-get-image-filename 'LOCALP 'NO-ERROR))) + (and (fboundp 'image-dired-dired-display-image) img-file)))) + +(define-key diredp-single-image-menu [diredp-image-dired-display-thumb] + '(menu-item "Go To Thumbnail" diredp-image-dired-display-thumb + :help "Pop to buffer showing the thumbnail of this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-create-thumb] + '(menu-item "Create Thumbnail" diredp-image-dired-create-thumb + :help "Create a thumbnail image for this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-edit-comment-and-tags] + '(menu-item "Edit Comment and Tags..." diredp-image-dired-edit-comment-and-tags + :help "Edit comment and tags for this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-delete-tag] + '(menu-item "Delete Image Tag..." diredp-image-dired-delete-tag + :help "Remove an `image-dired' tag from this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-tag-file] + '(menu-item "Add Tags..." diredp-image-dired-tag-file + :help "Add tags to this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-comment-file] + '(menu-item "Add Comment..." diredp-image-dired-comment-file + :help "Add a comment to this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-copy-with-exif-name] + '(menu-item "Copy with EXIF Name" diredp-image-dired-copy-with-exif-name + :help "Copy this image file to main image dir using EXIF name")) +(define-key diredp-single-image-menu [image-dired-dired-display-external] + '(menu-item "Display Externally" image-dired-dired-display-external + :help "Display image using external viewer")) +(define-key diredp-single-image-menu [image-dired-dired-display-image] + '(menu-item "Display to Fit Other Window" image-dired-dired-display-image + :help "Display scaled image to fit a separate window")) +(define-key diredp-single-image-menu [diredp-image-show-this-file] + '(menu-item "Display Full Size Or Smaller" diredp-image-show-this-file + :help "Display image full size or at least prefix-arg lines high")) +(define-key diredp-single-image-menu [dired-find-file] + '(menu-item "Display Full Size" dired-find-file + :help "Display image full size")) + + +;; `Single' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-single-encryption-menu (make-sparse-keymap "Encryption")) + (define-key diredp-menu-bar-single-menu [encryption] + (cons "Encryption" diredp-single-encryption-menu)) + + (define-key diredp-single-encryption-menu [diredp-decrypt-this-file] + '(menu-item "Decrypt..." (lambda () + (interactive) + (epa-decrypt-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)))) + :help "Decrypt this file")) + (define-key diredp-single-encryption-menu [diredp-verify-this-file] + '(menu-item "Verify..." (lambda () + (interactive) + (epa-verify-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)))) + :help "Verify this file")) + (define-key diredp-single-encryption-menu [diredp-sign-this-file] + '(menu-item "Sign..." (lambda () + (interactive) + (epa-sign-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)) + (epa-select-keys (epg-make-context) + "Select keys for signing. +If no one is selected, default secret key is used. " + nil t))) + :help "Encrypt this file")) + (define-key diredp-single-encryption-menu [diredp-encrypt-this-file] + '(menu-item "Encrypt..." (lambda () + (interactive) + (epa-encrypt-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)) + (epa-select-keys + (epg-make-context) + "Select recipients for encryption. +If no one is selected, symmetric encryption will be performed. " + nil t))) + :help "Sign this file"))) + + +;; `Single' > `Bookmark' menu. +;; +(when (require 'bookmark+ nil t) + (defvar diredp-single-bookmarks-menu (make-sparse-keymap "Bookmark")) + (define-key diredp-menu-bar-single-menu [bookmark] + (cons "Bookmark" diredp-single-bookmarks-menu)) + + (define-key diredp-single-bookmarks-menu [diredp-set-tag-value-this-file] + '(menu-item "Set Tag Value..." diredp-set-tag-value-this-file + :help "Set the value (not the name) of a given tag for this file")) + (define-key diredp-single-bookmarks-menu [diredp-paste-replace-tags-this-file] + '(menu-item "Paste Tags (Replace)" diredp-paste-replace-tags-this-file + :help "Replace tags for this file with previously copied tags")) + (define-key diredp-single-bookmarks-menu [diredp-paste-add-tags-this-file] + '(menu-item "Paste Tags (Add)" diredp-paste-add-tags-this-file + :help "Add previously copied tags to this file")) + (define-key diredp-single-bookmarks-menu [diredp-copy-tags-this-file] + '(menu-item "Copy Tags" diredp-copy-tags-this-file + :help "Copy the tags from this file, so you can paste them to another")) + (define-key diredp-single-bookmarks-menu [diredp-remove-all-tags-this-file] + '(menu-item "Remove All Tags" diredp-remove-all-tags-this-file + :help "Remove all tags from the file at cursor")) + (define-key diredp-single-bookmarks-menu [diredp-untag-this-file] + '(menu-item "Remove Tags..." diredp-untag-this-file + :help "Remove some tags from the file at cursor (`C-u': remove all tags)")) + (define-key diredp-single-bookmarks-menu [diredp-tag-this-file] + '(menu-item "Add Tags..." diredp-tag-this-file :help "Add some tags to the file at cursor")) + (define-key diredp-single-bookmarks-menu [diredp-bookmark-this-file] + '(menu-item "Bookmark..." diredp-bookmark-this-file + :help "Bookmark the file at cursor (create/set autofile)"))) + + +;; `Multiple' menu. +;; +;; REPLACE ORIGINAL "Operate" menu in `dired.el'. +;; +(defvar diredp-menu-bar-multiple-menu (make-sparse-keymap "Multiple")) +(define-key dired-mode-map [menu-bar operate] (cons "Multiple" diredp-menu-bar-multiple-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-menu 'diredp-menu-bar-multiple-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-menu 'diredp-menu-bar-multiple-menu) ; 2017-04-09 + +(define-key diredp-menu-bar-multiple-menu [diredp-describe-marked-autofiles] + '(menu-item "Describe Marked Autofiles" diredp-describe-marked-autofiles + :help "Show the metadata for the marked files that are autofiles" + :enable (featurep 'bookmark+))) +(define-key diredp-menu-bar-multiple-menu [separator-describe] '("--")) ; ----------------------- + +(unless (memq system-type '(windows-nt ms-dos)) + (define-key diredp-menu-bar-multiple-menu [chown] + '(menu-item "Change Owner..." dired-do-chown + :help "Change the owner of marked files"))) +(unless (memq system-type '(windows-nt ms-dos)) + (define-key diredp-menu-bar-multiple-menu [chgrp] + '(menu-item "Change Group..." dired-do-chgrp + :help "Change the owner of marked files"))) +(define-key diredp-menu-bar-multiple-menu [chmod] + '(menu-item "Change Mode..." dired-do-chmod + :help "Change mode (attributes) of marked files")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-menu-bar-multiple-menu [touch] + '(menu-item "Change Timestamp (`touch')..." dired-do-touch + :help "Change the timestamp of the marked files, using `touch'"))) +(define-key diredp-menu-bar-multiple-menu [separator-change] '("--")) ; ------------------------- + +(when (fboundp 'diredp-read-expression) ; Emacs 22+ + (define-key diredp-menu-bar-multiple-menu [diredp-do-lisp-sexp] + '(menu-item "Eval Sexp..." diredp-do-lisp-sexp + :help "Evaluate an Emacs-Lisp sexp in each marked file"))) +(define-key diredp-menu-bar-multiple-menu [diredp-do-emacs-command] + '(menu-item "Invoke Emacs Command..." diredp-do-emacs-command + :help "Invoke an Emacs command in each marked file")) +(define-key diredp-menu-bar-multiple-menu [diredp-do-apply-function] + '(menu-item "Apply Function..." diredp-do-apply-function + :help "Apply a Lisp function to each marked file name (`C-u': file contents, not name)")) +(define-key diredp-menu-bar-multiple-menu [print] + '(menu-item "Print..." dired-do-print :help "Print marked files, supplying print command")) +(define-key diredp-menu-bar-multiple-menu [compress] + '(menu-item "Compress/Uncompress" dired-do-compress :help "Compress/uncompress marked files")) +(when (fboundp 'dired-do-compress-to) + (define-key diredp-menu-bar-multiple-menu [compress-to] + '(menu-item "Compress to..." dired-do-compress-to + :help "Compress marked files and dirs together, in the same archive"))) +(define-key diredp-menu-bar-multiple-menu [command] + '(menu-item "Shell Command..." dired-do-shell-command + :help "Run a shell command on each marked file")) +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-menu-bar-multiple-menu [async-command] + '(menu-item "Asynchronous Shell Command..." dired-do-async-shell-command + :help "Run a shell command asynchronously on each marked file"))) +(define-key diredp-menu-bar-multiple-menu [compile] + '(menu-item "Byte Compile" dired-do-byte-compile :help "Byte-compile marked Emacs Lisp files")) +(define-key diredp-menu-bar-multiple-menu [load] + '(menu-item "Load" dired-do-load :help "Load marked Emacs Lisp files")) + +(unless (require 'bookmark+ nil t) + (define-key diredp-menu-bar-multiple-menu [diredp-bookmark-this-file] + '(menu-item "Bookmark..." diredp-bookmark-this-file :help "Bookmark the file at cursor"))) +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key diredp-menu-bar-multiple-menu [mkhtml-dired-files] + '(menu-item "Create HTML" mkhtml-dired-files + :help "Create HTML files corresponding to marked files"))) +(define-key diredp-menu-bar-multiple-menu [separator-misc] '("--")) ; --------------------------- + +(define-key diredp-menu-bar-multiple-menu [diredp-copy-abs-filenames-as-kill] + '(menu-item "Copy Marked Names as Absolute" diredp-copy-abs-filenames-as-kill + :help "Copy absolute names of marked files to the kill ring" + :keys "M-0 w")) +(define-key diredp-menu-bar-multiple-menu [kill-ring] + '(menu-item "Copy Marked Names" dired-copy-filename-as-kill + :help "Copy names of marked files to the kill ring, for pasting")) +(define-key diredp-menu-bar-multiple-menu [diredp-list-marked] + '(menu-item "List Marked Files" diredp-list-marked + :help "List the files marked here (C-u C-u: all, C-u C-u C-u: all + dirs)")) +(define-key diredp-menu-bar-multiple-menu [diredp-insert-subdirs] + '(menu-item "Insert Subdirs" diredp-insert-subdirs + :help "Insert the marked subdirectories - like using `i' at each marked dir")) +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(define-key diredp-menu-bar-multiple-menu [dired-multiple-w32-browser] + '(menu-item "Open Associated Windows Apps" dired-multiple-w32-browser + :help "Open files using the Windows apps associated with their file types"))) +(when (fboundp 'dired-do-find-marked-files) + (define-key diredp-menu-bar-multiple-menu [find-files] + '(menu-item "Open" dired-do-find-marked-files ; In `dired-x.el'. + :help "Open each marked file for editing"))) + + +;; `Multiple' > `Dired' menu. +;; +(defvar diredp-multiple-dired-menu (make-sparse-keymap "Dired") + "`Dired' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-dired] + `(menu-item "Dired" ,diredp-multiple-dired-menu + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only")) + +(define-key diredp-multiple-dired-menu [diredp-marked-other-window] + '(menu-item "Dired Marked in Other Window" diredp-marked-other-window + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only, in other window")) +(define-key diredp-multiple-dired-menu [diredp-marked] + '(menu-item "Dired Marked" diredp-marked + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only")) + + +;; `Multiple' > `Omit' menu. +;; +(defvar diredp-multiple-omit-menu (make-sparse-keymap "Omit") + "`Omit' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-omit] (cons "Omit" diredp-multiple-omit-menu)) + +(define-key diredp-multiple-omit-menu [omit-unmarked] + '(menu-item "Omit Unmarked" diredp-omit-unmarked :help "Hide lines of unmarked files")) +(define-key diredp-multiple-omit-menu [omit-marked] + '(menu-item "Omit Marked" diredp-omit-marked :help "Hide lines of marked files")) + + +;; `Multiple' > `Delete' menu. +;; +(defvar diredp-multiple-delete-menu (make-sparse-keymap "Delete") + "`Delete' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-delete] (cons "Delete" diredp-multiple-delete-menu)) + +(define-key diredp-multiple-delete-menu [delete-flagged] + '(menu-item "Delete Flagged" dired-do-flagged-delete + :help "Delete all files flagged for deletion (D)")) +(define-key diredp-multiple-delete-menu [delete] + '(menu-item "Delete Marked (not Flagged)" dired-do-delete + :help "Delete current file or all marked files (not flagged files)")) + + +;; `Multiple' > `Rename' menu. +;; +(defvar diredp-multiple-rename-menu (make-sparse-keymap "Rename") + "`Rename' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-case] (cons "Rename" diredp-multiple-rename-menu)) + +(define-key diredp-multiple-rename-menu [multiple-rename-rename] + '(menu-item "Move to Dir... / Rename This..." dired-do-rename + :help "Move marked (or next N) files, or rename current file")) + +(define-key diredp-multiple-rename-menu [multiple-rename-capitalize] + '(menu-item "Capitalize" diredp-capitalize + :help "Capitalize (initial caps) the names of all marked files")) +(define-key diredp-multiple-rename-menu [multiple-rename-downcase] + '(menu-item "Downcase" dired-downcase + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files to lowercase names")) +(define-key diredp-multiple-rename-menu [multiple-rename-upcase] + '(menu-item "Upcase" dired-upcase + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files to uppercase names")) + + +;; `Multiple' > `Move / Copy / Link' menu. +;; +(defvar diredp-multiple-move-copy-link-menu (make-sparse-keymap "Move / Copy / Link") + "`Move / Copy / Link' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-move-copy-link] + (cons "Move / Copy / Link" diredp-multiple-move-copy-link-menu)) + +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-hardlink] + '(menu-item "Hardlink to..." dired-do-hardlink + :help "Make hard links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-symlink] + '(menu-item "Symlink to (Absolute)..." dired-do-symlink ; In `dired-aux.el'. + :help "Make absolute symbolic links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-relsymlink] + '(menu-item "Symlink to (Relative)..." dired-do-relsymlink ; In `dired-x.el'. + :help "Make relative symbolic links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-copy] + '(menu-item "Copy to..." dired-do-copy :help "Copy current file or all marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-rename] + '(menu-item "Move to..." dired-do-rename :help "Rename current file or move marked files")) + + +;; `Multiple' > `Images' menu. +;; +(defvar diredp-multiple-images-menu (make-sparse-keymap "Images")) +(defalias 'diredp-multiple-images-menu diredp-multiple-images-menu) +(define-key diredp-menu-bar-multiple-menu [images] + '(menu-item "Images" diredp-multiple-images-menu + :enable (fboundp 'image-dired-display-thumbs))) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-images-menu 'diredp-multiple-images-menu)) +(make-obsolete-variable 'diredp-menu-bar-images-menu 'diredp-multiple-images-menu) ; 2017-04-09 + +;; Remove the items from `Multiple' menu. +(define-key diredp-menu-bar-multiple-menu [image-dired-delete-tag] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-tag-files] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-dired-comment-files] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-display-thumbs] nil) + +;; Add them to `Multiple' > `Images' menu. +(define-key diredp-multiple-images-menu [image-dired-delete-tag] + '(menu-item "Delete Tag..." image-dired-delete-tag + :help "Delete tag from marked image files")) +(define-key diredp-multiple-images-menu [image-dired-tag-files] + '(menu-item "Add Tags..." image-dired-tag-files + :help "Add tags to marked image files")) +(define-key diredp-multiple-images-menu [image-dired-dired-comment-files] + '(menu-item "Add Comment..." image-dired-dired-comment-files + :help "Add comment to marked image files")) +(define-key diredp-multiple-images-menu [image-dired-display-thumbs] + '(menu-item "Display Thumbnails" image-dired-display-thumbs + :help "Display thumbnails for marked image files")) +(define-key diredp-multiple-images-menu [diredp-do-display-images] + '(menu-item "Display" diredp-do-display-images + :help "Display the marked image files")) + + +;; `Multiple' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-multiple-encryption-menu (make-sparse-keymap "Encryption")) + (define-key diredp-menu-bar-multiple-menu [encryption] + (cons "Encryption" diredp-multiple-encryption-menu)) + + ;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs + ;; works for newer Emacs too. + (when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu)) + (make-obsolete-variable 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu) ; 2017-04-09 + + (when (boundp 'diredp-menu-bar-encryption-menu) + (defalias 'diredp-menu-bar-encryption-menu diredp-menu-bar-encryption-menu)) + (make-obsolete 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu) ; 2017-04-09 + + ;; Remove the items from `Multiple' menu. + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-decrypt] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-verify] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-sign] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-encrypt] nil) + + ;; Add them to `Multiple' > `Encryption' menu. + (define-key diredp-multiple-encryption-menu [epa-dired-do-decrypt] + '(menu-item "Decrypt..." epa-dired-do-decrypt :help "Decrypt the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-verify] + '(menu-item "Verify..." epa-dired-do-verify :help "Verify the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-sign] + '(menu-item "Sign..." epa-dired-do-sign :help "Sign the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-encrypt] + '(menu-item "Encrypt..." epa-dired-do-encrypt :help "Encrypt the marked files"))) + + +;; `Multiple' > `Search' menu. +;; +(defvar diredp-multiple-search-menu (make-sparse-keymap "Search")) +(define-key diredp-menu-bar-multiple-menu [search] + (cons "Search" diredp-multiple-search-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-search-menu 'diredp-multiple-search-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-search-menu 'diredp-multiple-search-menu) ; 2017-04-09 + +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + (define-key diredp-multiple-search-menu [isearch-regexp] + '(menu-item "Isearch Regexp Files..." dired-do-isearch-regexp + :help "Incrementally search marked files for regexp")) + (define-key diredp-multiple-search-menu [isearch] + '(menu-item "Isearch Files..." dired-do-isearch + :help "Incrementally search marked files for string"))) +(when (fboundp 'dired-do-find-regexp-and-replace) + (define-key diredp-multiple-search-menu [find-query-replace] + '(menu-item "Query Replace Using `find'..." dired-do-find-regexp-and-replace + :help "Replace regexp in marked files using `find'"))) +(define-key diredp-multiple-search-menu [query-replace] + (if (< emacs-major-version 21) + '(menu-item "Query Replace Using TAGS Table..." dired-do-query-replace) + '(menu-item "Query Replace Using TAGS Table..." dired-do-query-replace-regexp + :help "Replace regexp in marked files using tags in a TAGS table"))) +(when (fboundp 'dired-do-find-regexp) + (define-key diredp-multiple-search-menu [find-regexp] + '(menu-item "Search Files Using `find'..." dired-do-find-regexp + :help "Search marked files for regexp using `find'"))) +(define-key diredp-multiple-search-menu [search] + '(menu-item "Search Files Using TAGS Table..." dired-do-search + :help "Search marked files for regexp using tags in a TAGS table")) +(define-key diredp-multiple-search-menu [grep] + '(menu-item "Grep..." diredp-do-grep :help "Grep marked, next N, or all files shown")) + + +;; `Multiple' > `Bookmark' menu. +;; +(defvar diredp-multiple-bookmarks-menu (make-sparse-keymap "Bookmark")) +(define-key diredp-menu-bar-multiple-menu [bookmark] + (cons "Bookmark" diredp-multiple-bookmarks-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-bookmarks-menu 'diredp-multiple-bookmarks-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-bookmarks-menu 'diredp-multiple-bookmarks-menu) ; 2017-04-09 + +(when (require 'bookmark+ nil t) + (define-key diredp-multiple-bookmarks-menu [diredp-do-set-tag-value] + '(menu-item "Set Tag Value..." diredp-do-set-tag-value + :help "Set the value of a given tag for the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-paste-replace-tags] + '(menu-item "Paste Tags (Replace)" diredp-do-paste-replace-tags + :help "Replace tags for the marked or next N files with copied tags")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-paste-add-tags] + '(menu-item "Paste Tags (Add)" diredp-do-paste-add-tags + :help "Add previously copied tags to the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-remove-all-tags] + '(menu-item "Remove All Tags" diredp-do-remove-all-tags + :help "Remove all tags from the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-untag] + '(menu-item "Remove Tags..." diredp-do-untag + :help "Remove some tags from the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-tag] + '(menu-item "Add Tags..." diredp-do-tag + :help "Add some tags to the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [separator-book-2] '("--"))) ; ------------ + +(define-key diredp-multiple-bookmarks-menu + [diredp-do-bookmark-in-bookmark-file-recursive] + '(menu-item "Bookmark in Bookmark File (Here and Below)..." + diredp-do-bookmark-in-bookmark-file-recursive + :help "Bookmark marked files (including in marked subdirs) in bookmark file and save it")) +(define-key diredp-multiple-bookmarks-menu + [diredp-set-bookmark-file-bookmark-for-marked-recursive] + '(menu-item "Create Bookmark-File Bookmark (Here and Below)..." + diredp-set-bookmark-file-bookmark-for-marked-recursive + :help "Create a bookmark-file bookmark for marked files, including in marked subdirs")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs (Here and Below)..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-recursive] + '(menu-item "Bookmark (Here and Below)..." diredp-do-bookmark-recursive + :help "Bookmark the marked files, including those in marked subdirs")) +(define-key diredp-multiple-bookmarks-menu [separator-book-1] '("--")) ; --------------- + +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-in-bookmark-file] + '(menu-item "Bookmark in Bookmark File..." diredp-do-bookmark-in-bookmark-file + :help "Bookmark the marked files in BOOKMARK-FILE and save BOOKMARK-FILE")) +(define-key diredp-multiple-bookmarks-menu [diredp-set-bookmark-file-bookmark-for-marked] + '(menu-item "Create Bookmark-File Bookmark..." diredp-set-bookmark-file-bookmark-for-marked + :help "Create a bookmark-file bookmark, and bookmark the marked files in it")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark] + '(menu-item "Bookmark..." diredp-do-bookmark :help "Bookmark the marked or next N files")) + + +;; `Multiple' > `Marked Here and Below' menu. +;; +(defvar diredp-multiple-recursive-menu (make-sparse-keymap "Marked Here and Below")) +(define-key diredp-menu-bar-multiple-menu [operate-recursive] + (cons "Marked Here and Below" diredp-multiple-recursive-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-recursive-menu 'diredp-multiple-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-recursive-menu 'diredp-multiple-recursive-menu) ; 2017-04-09 + +(when (fboundp 'diredp-do-chown-recursive) + (define-key diredp-multiple-recursive-menu [chown] + '(menu-item "Change Owner..." diredp-do-chown-recursive + :help "Change the owner of marked files, including those in marked subdirs"))) +(when (fboundp 'diredp-do-chgrp-recursive) + (define-key diredp-multiple-recursive-menu [chgrp] + '(menu-item "Change Group..." diredp-do-chgrp-recursive + :help "Change the owner of marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [chmod] + '(menu-item "Change Mode..." diredp-do-chmod-recursive + :help "Change mode (attributes) of marked files, including those in marked subdirs")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-multiple-recursive-menu [touch] + '(menu-item "Change Timestamp (`touch')..." diredp-do-touch-recursive + :help "Change timestamp of marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [separator-change] '("--")) ; ---------------- + +(define-key diredp-multiple-recursive-menu [diredp-do-apply-function-recursive] + '(menu-item "Apply Lisp Function..." diredp-do-apply-function-recursive + :help "Apply a Lisp function to the marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-print-recursive] + '(menu-item "Print..." diredp-do-print-recursive + :help "Print the marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-shell-command-recursive] + '(menu-item "Shell Command..." diredp-do-shell-command-recursive + :help "Run shell command on the marked files, including those in marked subdirs")) +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-multiple-recursive-menu [diredp-do-async-shell-command-recursive] + '(menu-item "Asynchronous Shell Command..." diredp-do-async-shell-command-recursive + :help "Run shell command asynchronously on marked files, including in marked subdirs"))) + +(when (fboundp 'diredp-unmark-all-marks-recursive) ; Emacs 22+ + (define-key diredp-multiple-recursive-menu [separator-1] '("--")) ; ------------ + (define-key diredp-multiple-recursive-menu [diredp-change-marks-recursive] + '(menu-item "Change Mark..." diredp-change-marks-recursive + :help "Change all OLD marks to NEW marks, including those in marked subdirs")) + (define-key diredp-multiple-recursive-menu [diredp-unmark-all-files-recursive] + '(menu-item "Unmark Marked-With..." diredp-unmark-all-files-recursive + :help "Remove a given mark everywhere, including in marked subdirs")) + (define-key diredp-multiple-recursive-menu [diredp-unmark-all-marks-recursive] + '(menu-item "Unmark All..." diredp-unmark-all-marks-recursive + :help "Remove ALL marks everywhere, including in marked subdirs"))) + +(define-key diredp-multiple-recursive-menu [separator-misc] '("--")) ; ------------------ + +(define-key diredp-multiple-recursive-menu [diredp-do-delete-recursive] + '(menu-item "Delete Marked (not Flagged)" diredp-do-delete-recursive + :help "Delete marked (not flagged) files, including in marked subdirs")) +(define-key diredp-multiple-recursive-menu [separator-delete] '("--")) ; ---------------- + +(define-key diredp-multiple-recursive-menu [diredp-do-hardlink-recursive] + '(menu-item "Hardlink to..." diredp-do-hardlink-recursive + :help "Make hard links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-symlink-recursive] + '(menu-item "Symlink to (Absolute)..." diredp-do-symlink-recursive + :help "Make absolute symbolic links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-relsymlink-recursive] + '(menu-item "Symlink to (Relative)..." diredp-do-relsymlink-recursive + :help "Make relative symbolic links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-copy-recursive] + '(menu-item "Copy to..." diredp-do-copy-recursive + :help "Copy marked files, including in marked subdirs, to a given directory")) +(define-key diredp-multiple-recursive-menu [diredp-do-move-recursive] + '(menu-item "Move to..." diredp-do-move-recursive + :help "Move marked files, including in marked subdirs, to a given directory")) +(define-key diredp-multiple-recursive-menu [separator-copy-move] '("--")) ; ------------- + +(define-key diredp-multiple-recursive-menu [diredp-capitalize-recursive] + '(menu-item "Capitalize" diredp-capitalize-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Capitalize the names of all marked files, including in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-downcase-recursive] + '(menu-item "Downcase" diredp-downcase-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files, including in marked subdirs, to lowercase names")) +(define-key diredp-multiple-recursive-menu [diredp-upcase-recursive] + '(menu-item "Upcase" diredp-upcase-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files, including in marked subdirs, to uppercase names")) +(define-key diredp-multiple-recursive-menu [separator-lettercase] '("--")) ; ------------ + +(define-key diredp-multiple-recursive-menu [diredp-list-marked-recursive] + '(menu-item "List Marked Files" diredp-list-marked-recursive + :help "List the files marked here and in marked subdirs, recursively")) +(define-key diredp-multiple-recursive-menu [diredp-copy-filename-as-kill-recursive] + '(menu-item "Copy File Names (to Paste)" diredp-copy-filename-as-kill-recursive + :help "Copy names of files marked here and in marked subdirs, to `kill-ring'")) +(define-key diredp-multiple-recursive-menu [diredp-insert-subdirs-recursive] + '(menu-item "Insert Subdirs" diredp-insert-subdirs-recursive + :help "Insert the marked subdirectories, gathered recursively")) +(define-key diredp-multiple-recursive-menu [separator-dirs] '("--")) ; ------------------ + +(define-key diredp-multiple-recursive-menu [diredp-marked-recursive-other-window] + '(menu-item "Dired (Marked) in Other Window" diredp-marked-recursive-other-window + :help "Open Dired (in other window) on marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-marked-recursive] + '(menu-item "Dired (Marked)" diredp-marked-recursive + :help "Open Dired on marked files, including those in marked subdirs")) +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(define-key diredp-multiple-recursive-menu [diredp-multiple-w32-browser-recursive] + '(menu-item "Open Associated Windows Apps" diredp-multiple-w32-browser-recursive + :help "Run Windows apps for with marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [diredp-do-find-marked-files-recursive] + '(menu-item "Open" diredp-do-find-marked-files-recursive + :help "Find marked files simultaneously, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Images' menu. +;; +(defvar diredp-images-recursive-menu (make-sparse-keymap "Images")) +(defalias 'diredp-images-recursive-menu diredp-images-recursive-menu) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-images-recursive-menu) + (defalias 'diredp-menu-bar-images-recursive-menu diredp-menu-bar-images-recursive-menu)) +(make-obsolete 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu) ; 2017-04-09 + +(define-key diredp-multiple-recursive-menu [images] + '(menu-item "Images" diredp-images-recursive-menu + :enable (fboundp 'image-dired-delete-tag))) +(define-key diredp-images-recursive-menu [diredp-image-dired-delete-tag-recursive] + '(menu-item "Delete Image Tag..." diredp-image-dired-delete-tag-recursive + :help "Remove an `image-dired' tag from marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-tag-files-recursive] + '(menu-item "Add Image Tags..." diredp-image-dired-tag-files-recursive + :help "Add `image-dired' tags to marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-comment-files-recursive] + '(menu-item "Add Image Comment..." diredp-image-dired-comment-files-recursive + :help "Add image comment to marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-display-thumbs-recursive] + '(menu-item "Display Image Thumbnails" diredp-image-dired-display-thumbs-recursive + :help "Show thumbnails for marked image files, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-menu-bar-encryption-recursive-menu (make-sparse-keymap "Encryption")) + (define-key diredp-multiple-recursive-menu [encryption] + (cons "Encryption" diredp-menu-bar-encryption-recursive-menu)) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-decrypt-recursive] + '(menu-item "Decrypt..." diredp-do-decrypt-recursive + :help "Decrypt marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-verify-recursive] + '(menu-item "Verify..." diredp-do-verify-recursive + :help "Verify marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-sign-recursive] + '(menu-item "Sign..." diredp-do-sign-recursive + :help "Sign marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-encrypt-recursive] + '(menu-item "Encrypt..." diredp-do-encrypt-recursive + :help "Encrypt marked files, including those in marked subdirs"))) + + +;; `Multiple' > `Marked Here and Below' > `Search' menu. +;; +(defvar diredp-menu-bar-search-recursive-menu (make-sparse-keymap "Search")) +(define-key diredp-multiple-recursive-menu [search] + (cons "Search" diredp-menu-bar-search-recursive-menu)) +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + (define-key diredp-menu-bar-search-recursive-menu [diredp-do-isearch-regexp-recursive] + '(menu-item "Isearch Regexp Files..." diredp-do-isearch-regexp-recursive + :help "Incrementally regexp search marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-search-recursive-menu [diredp-do-isearch-recursive] + '(menu-item "Isearch Files..." diredp-do-isearch-recursive + :help "Incrementally search marked files, including those in marked subdirs"))) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-query-replace-regexp-recursive] + '(menu-item "Query Replace..." diredp-do-query-replace-regexp-recursive + :help "Replace regexp in marked files, including those in marked subdirs")) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-search-recursive] + '(menu-item "Search Files..." diredp-do-search-recursive + :help "Regexp search marked files, including those in marked subdirs")) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-grep-recursive] + '(menu-item "Grep..." diredp-do-grep-recursive + :help "Run `grep' on the marked files, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Bookmark' menu. +;; +(defvar diredp-menu-bar-bookmarks-recursive-menu (make-sparse-keymap "Bookmark")) +(define-key diredp-multiple-recursive-menu [bookmarks] + (cons "Bookmark" diredp-menu-bar-bookmarks-recursive-menu)) +(define-key diredp-menu-bar-bookmarks-recursive-menu + [diredp-do-bookmark-in-bookmark-file-recursive] + '(menu-item "Bookmark in Bookmark File..." diredp-do-bookmark-in-bookmark-file-recursive + :help "Bookmark marked files, including those in marked subdirs, in a bookmark file")) +(define-key diredp-menu-bar-bookmarks-recursive-menu + [diredp-set-bookmark-file-bookmark-for-marked-recursive] + '(menu-item "Create Bookmark-File Bookmark..." + diredp-set-bookmark-file-bookmark-for-marked-recursive + :help "Create a bookmark-file bookmark for marked files, including in marked subdirs")) +(define-key diredp-menu-bar-bookmarks-recursive-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-menu-bar-bookmarks-recursive-menu [diredp-do-bookmark-recursive] + '(menu-item "Bookmark..." diredp-do-bookmark-recursive + :help "Bookmark the marked files, including those in marked subdirs")) + + + +;; `Regexp' menu. +;; +;; REPLACE ORIGINAL `Regexp' menu in `dired.el'. +;; +(defvar diredp-menu-bar-regexp-menu (make-sparse-keymap "Regexp")) +(define-key dired-mode-map [menu-bar regexp] (cons "Regexp" diredp-menu-bar-regexp-menu)) + +(define-key diredp-menu-bar-regexp-menu [hardlink] + '(menu-item "Hardlink to..." dired-do-hardlink-regexp ; In `dired-aux.el'. + :help "Make hard links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [symlink] + '(menu-item "Symlink to (Absolute)..." dired-do-symlink-regexp ; In `dired-aux.el'. + :help "Make absolute symbolic links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [relsymlink] + '(menu-item "Symlink to (Relative)..." dired-do-relsymlink-regexp ; In `dired-x.el'. + :help "Make relative symbolic links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [copy] + '(menu-item "Copy to..." dired-do-copy-regexp ; In `dired-aux.el'. + :help "Copy marked files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [rename] + '(menu-item "Move to..." dired-do-rename-regexp ; In `dired-aux.el'. + :help "Move marked files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [flag] + '(menu-item "Flag..." dired-flag-files-regexp :help "Flag files matching regexp for deletion")) +(define-key diredp-menu-bar-regexp-menu [image-dired-mark-tagged-files] + '(menu-item "Mark Image Files Tagged..." image-dired-mark-tagged-files + :enable (fboundp 'image-dired-mark-tagged-files) + :help "Mark image files whose image tags match regexp")) +(define-key diredp-menu-bar-regexp-menu [mark-cont] + '(menu-item "Mark Containing..." dired-mark-files-containing-regexp + :help "Mark files whose contents matches regexp")) +(define-key diredp-menu-bar-regexp-menu [mark] + '(menu-item "Mark..." dired-mark-files-regexp + :help "Mark files matching regexp")) + + +;; `Regexp' > `Here and Below' menu. +;; +(defvar diredp-regexp-recursive-menu (make-sparse-keymap "Here and Below")) +(define-key diredp-menu-bar-regexp-menu [mark-recursive] + (cons "Here and Below" diredp-regexp-recursive-menu)) +(define-key diredp-regexp-recursive-menu [diredp-mark-files-regexp-recursive] + '(menu-item "Mark Named..." diredp-mark-files-regexp-recursive + :help "Mark all file names matching a regexp, including those in marked subdirs")) +(define-key diredp-regexp-recursive-menu [diredp-mark-files-containing-regexp-recursive] + '(menu-item "Mark Containing..." diredp-mark-files-containing-regexp-recursive + :help "Mark all files with content matching a regexp, including in marked subdirs")) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-regexp-recursive-menu) + (defalias 'diredp-menu-bar-regexp-recursive-menu diredp-menu-bar-regexp-recursive-menu)) +(make-obsolete 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu) ; 2017-04-09 + + +;; "Marks" menu. +;; +;; REPLACE ORIGINAL `Marks' menu in `dired.el'. +;; +(defvar diredp-menu-bar-marks-menu (make-sparse-keymap "Marks")) +(define-key dired-mode-map [menu-bar mark] (cons "Marks" diredp-menu-bar-marks-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-mark-menu 'diredp-menu-bar-marks-menu)) +(make-obsolete-variable 'diredp-menu-bar-mark-menu 'diredp-menu-bar-marks-menu) ; 2017-04-09 + +(define-key diredp-menu-bar-marks-menu [prev] + '(menu-item "Previous Marked" dired-prev-marked-file :help "Move to previous marked file")) +(define-key diredp-menu-bar-marks-menu [next] + '(menu-item "Next Marked" dired-next-marked-file :help "Move to next marked file")) +(define-key diredp-menu-bar-marks-menu [marks] + '(menu-item "Change Mark..." dired-change-marks + :help "Replace a given mark character with another")) +(define-key diredp-menu-bar-marks-menu [toggle-marks] + (if (> emacs-major-version 21) + '(menu-item "Toggle Marked/Unmarked" dired-toggle-marks + :help "Mark unmarked files, unmark marked ones") + '(menu-item "Toggle Marked/Unmarked" dired-toggle-marks + :help "Mark unmarked files, unmark marked ones"))) + + +;; `Marks' > `Tagged' menu. +;; +(when (require 'bookmark+ nil t) + (defvar diredp-marks-tags-menu (make-sparse-keymap "Tagged (Autofiles)") + "`Tags' submenu for Dired menu-bar `Marks' menu.") + (define-key diredp-menu-bar-marks-menu [mark-tags] (cons "Tagged" diredp-marks-tags-menu)) + + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-none] + '(menu-item "Unmark Not Tagged with Any..." diredp-unmark-files-tagged-none + :help "Unmark files that are not tagged with *any* of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-not-all] + '(menu-item "Unmark Not Tagged with All..." diredp-unmark-files-tagged-not-all + :help "Unmark files that are not tagged with *all* tags")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-some] + '(menu-item "Unmark Tagged with Some..." diredp-unmark-files-tagged-some + :help "Unmark files that are tagged with at least one of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-all] + '(menu-item "Unmark Tagged with All..." diredp-unmark-files-tagged-all + :help "Unmark files that are tagged with *each* tag you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-regexp] + '(menu-item "Unmark Tagged Matching Regexp..." diredp-unmark-files-tagged-regexp + :help "Unmark files that have at least one tag that matches a regexp")) + (define-key diredp-marks-tags-menu [separator-marks-tags] '("--")) ; ------------------------- + + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-none] + '(menu-item "Mark Not Tagged with Any..." diredp-mark-files-tagged-none + :help "Mark files that are not tagged with *any* of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-not-all] + '(menu-item "Mark Not Tagged with All..." diredp-mark-files-tagged-not-all + :help "Mark files that are not tagged with *all* tags")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-some] + '(menu-item "Mark Tagged with Some..." diredp-mark-files-tagged-some + :help "Mark files that are tagged with at least one of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-all] + '(menu-item "Mark Tagged with All..." diredp-mark-files-tagged-all + :help "Mark files that are tagged with *each* tag you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-regexp] + '(menu-item "Mark Tagged Matching Regexp..." diredp-mark-files-tagged-regexp + :help "Mark files that have at least one tag that matches a regexp"))) + + +;; `Marks' > `Omit' menu. +;; +(defvar diredp-marks-omit-menu (make-sparse-keymap "Omit") + "`Omit' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [marks-omit] (cons "Omit" diredp-marks-omit-menu)) + +(define-key diredp-marks-omit-menu [marks-omit-unmarked] + '(menu-item "Omit Unmarked" diredp-omit-unmarked :help "Hide lines of unmarked files")) +(define-key diredp-marks-omit-menu [marks-omit-marked] + '(menu-item "Omit Marked" diredp-omit-marked :help "Hide lines of marked files")) + + +;; `Marks' > `Flag' menu. +;; +(defvar diredp-marks-flag-menu (make-sparse-keymap "Flag") + "`Flag' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [mark-flag] (cons "Flag" diredp-marks-flag-menu)) + +(define-key diredp-marks-flag-menu [marks-flag-extension] + '(menu-item "Flag Extension..." dired-flag-extension ; In `dired-x.el' + :help "Flag all files that have a certain extension, for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-garbage-files] + '(menu-item "Flag Garbage Files" dired-flag-garbage-files + :help "Flag unneeded files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-backup-files] + '(menu-item "Flag Backup Files" dired-flag-backup-files + :help "Flag all backup files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-auto-save-files] + '(menu-item "Flag Auto-save Files" dired-flag-auto-save-files + :help "Flag auto-save files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-region] + '(menu-item "Flag Region" diredp-flag-region-files-for-deletion + :visible (diredp-nonempty-region-p) + :help "Flag all files in the region (selection) for deletion")) +(when (< emacs-major-version 21) + (put 'diredp-flag-region-files-for-deletion 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-flag-menu [marks-flag-deletion] + '(menu-item "Flag This" dired-flag-file-deletion + :visible (not (diredp-nonempty-region-p)) + :help "Flag current line's file for deletion")) + + +;; `Marks' > `Unmark' menu. +;; +(defvar diredp-marks-unmark-menu (make-sparse-keymap "Unmark") + "`Unmark' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [mark-mark] (cons "Unmark" diredp-marks-unmark-menu)) + +(define-key diredp-marks-unmark-menu [unmark-autofiles] + '(menu-item "Unmark Autofiles" diredp-unmark-autofiles + :help "Unmark all autofiles (bookmarks with same name as file)" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-unmark-menu [unmark-all] + '(menu-item "Unmark All" dired-unmark-all-marks :help "Remove all marks from all files")) +(define-key diredp-marks-unmark-menu [unmark-with] + '(menu-item "Unmark Marked-With..." dired-unmark-all-files + :help "Remove a specific mark (or all marks) from every file")) +(define-key diredp-marks-unmark-menu [unmark-region] + '(menu-item "Unmark Region" diredp-unmark-region-files + :visible (diredp-nonempty-region-p) + :help "Unmark all files in the region (selection)")) +(when (< emacs-major-version 21) + (put 'diredp-unmark-region-files 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-unmark-menu [unmark-this] + '(menu-item "Unmark This" dired-unmark + :visible (not (diredp-nonempty-region-p)) + :help "Unmark or unflag current line's file")) + + +;; `Marks' > `Mark' menu. +;; +(defvar diredp-marks-mark-menu (make-sparse-keymap "Mark") + "`Mark' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [marks-mark] (cons "Mark" diredp-marks-mark-menu)) + +(define-key diredp-marks-mark-menu [marks-mark-sexp] + '(menu-item "Mark If..." dired-mark-sexp ; In `dired-x.el'. + :help "Mark files that satisfy specified condition")) +(define-key diredp-marks-mark-menu [marks-image-dired-mark-tagged-files] + '(menu-item "Mark Image Files Tagged..." image-dired-mark-tagged-files + :enable (fboundp 'image-dired-mark-tagged-files) ; In `image-dired.el'. + :help "Mark image files whose image tags match regexp")) +(define-key diredp-marks-mark-menu [marks-mark-cont] + '(menu-item "Mark Content Matching Regexp..." dired-mark-files-containing-regexp + :help "Mark files whose contents matches regexp")) +(define-key diredp-marks-mark-menu [marks-mark...] + '(menu-item "Mark Name Matching Regexp..." dired-mark-files-regexp + :help "Mark file names matching regexp")) +(when (fboundp 'dired-mark-omitted) ; In `dired-x.el', Emacs 22+. + (define-key diredp-marks-mark-menu [marks-mark-omitted] + '(menu-item "Mark Omitted..." dired-mark-omitted + :help "Mark all omitted files and subdirectories"))) +(define-key diredp-marks-mark-menu [marks-mark-extension] + '(menu-item "Mark Extension..." diredp-mark/unmark-extension + :help "Mark all files with specified extension")) +(define-key diredp-marks-mark-menu [marks-mark-autofiles] + '(menu-item "Mark Autofiles" diredp-mark-autofiles + :help "Mark all autofiles (bookmarks with same name as file)" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-mark-menu [marks-mark-symlinks] + '(menu-item "Mark Symlinks" dired-mark-symlinks + :visible (fboundp 'make-symbolic-link) :help "Mark all symbolic links")) +(define-key diredp-marks-mark-menu [marks-mark-directories] + '(menu-item "Mark Directories" dired-mark-directories + :help "Mark all directories except `.' and `..'")) +(define-key diredp-marks-mark-menu [marks-mark-directory] + '(menu-item "Mark Old Backups" dired-clean-directory + :help "Flag old numbered backups for deletion")) +(define-key diredp-marks-mark-menu [marks-mark-executables] + '(menu-item "Mark Executables" dired-mark-executables :help "Mark all executable files")) +(define-key diredp-marks-mark-menu [marks-mark-region] + '(menu-item "Mark Region" diredp-mark-region-files + :visible (diredp-nonempty-region-p) + :help "Mark all of the files in the region (selection)")) +(when (< emacs-major-version 21) + (put 'diredp-mark-region-files 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-mark-menu [marks-mark-this] + '(menu-item "Mark This" dired-mark + :visible (not (diredp-nonempty-region-p)) + :help "Mark current line's file for future operations")) + + +;; `Marks' > `Here and Below' menu. +;; +(defvar diredp-marks-recursive-menu (make-sparse-keymap "Here and Below")) +(define-key diredp-menu-bar-marks-menu [mark-recursive] + (cons "Here and Below" diredp-marks-recursive-menu)) + +(define-key diredp-marks-recursive-menu [diredp-flag-auto-save-files-recursive] + '(menu-item "Flag Auto-Save Files..." diredp-flag-auto-save-files-recursive + :help "Flag all auto-save files for deletion, including those in marked subdirs")) +(when (fboundp 'diredp-unmark-all-marks-recursive) ; Emacs 22+ + (define-key diredp-marks-recursive-menu [diredp-change-marks-recursive] + '(menu-item "Change Mark..." diredp-change-marks-recursive + :help "Change all OLD marks to NEW marks, including those in marked subdirs")) + (define-key diredp-marks-recursive-menu [diredp-unmark-all-files-recursive] + '(menu-item "Unmark Marked-With..." diredp-unmark-all-files-recursive + :help "Remove a given mark everywhere, including in marked subdirs")) + (define-key diredp-marks-recursive-menu [diredp-unmark-all-marks-recursive] + '(menu-item "Unmark All..." diredp-unmark-all-marks-recursive + :help "Remove ALL marks everywhere, including in marked subdirs")) + (define-key diredp-marks-recursive-menu [separator-1] '("--"))) ; ------------ +(define-key diredp-marks-recursive-menu [diredp-mark-sexp-recursive] + '(menu-item "If..." diredp-mark-sexp-recursive + :help "Mark files satisfying specified condition, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-files-containing-regexp-recursive] + '(menu-item "Containing Regexp..." diredp-mark-files-containing-regexp-recursive + :help "Mark all files with content matching a regexp, including in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-files-regexp-recursive] + '(menu-item "Named Regexp..." diredp-mark-files-regexp-recursive + :help "Mark all file names matching a regexp, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-extension-recursive] + '(menu-item "Extension..." diredp-mark-extension-recursive + :help "Mark all files with a given extension, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-autofiles-recursive] + '(menu-item "Autofiles" diredp-mark-autofiles-recursive + :help "Mark all files with a given extension, including those in marked subdirs" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-recursive-menu [diredp-mark-symlinks-recursive] + '(menu-item "Symbolic Links" diredp-mark-symlinks-recursive + :help "Mark all symbolic links, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-directories-recursive] + '(menu-item "Directories" diredp-mark-directories-recursive + :help "Mark all directories, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-executables-recursive] + '(menu-item "Executables" diredp-mark-executables-recursive + :help "Mark all executable files, including those in marked subdirs")) + + +;; "Dir" menu. +;; +;; REPLACE ORIGINAL `Subdir' menu in `dired.el'. +;; +(defvar diredp-menu-bar-dir-menu (make-sparse-keymap "Dir")) +(define-key dired-mode-map [menu-bar subdir] (cons "Dir" diredp-menu-bar-dir-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-subdir-menu 'diredp-dir-menu)) +(make-obsolete-variable 'diredp-menu-bar-subdir-menu 'diredp-dir-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-subdir-menu) + (defalias 'diredp-menu-bar-subdir-menu diredp-menu-bar-subdir-menu)) +(make-obsolete 'diredp-menu-bar-subdir-menu 'diredp-dir-menu) ; 2017-04-09 + + +;; `Dir' > `Hide/Show' menu. +;; +(defvar diredp-hide/show-menu (make-sparse-keymap "Hide/Show") + "`Hide/Show' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [hide-show] (cons "Hide/Show" diredp-hide/show-menu)) + +(when (fboundp 'dired-omit-mode) + (define-key diredp-hide/show-menu [dired-omit-mode] + '(menu-item "Hide/Show Uninteresting (Omit Mode)" dired-omit-mode + :help "Toggle omission of uninteresting files (Omit mode)"))) +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (define-key diredp-hide/show-menu [hide-details] + '(menu-item "Hide/Show Details" dired-hide-details-mode + :help "Hide or show less important fields of directory listing"))) +(define-key diredp-hide/show-menu [hide-all] + '(menu-item "Hide/Show All Subdirs" dired-hide-all + :help "Hide all subdirectories, leave only header lines")) +(define-key diredp-hide/show-menu [hide-subdir] + '(menu-item "Hide/Show Subdir" diredp-hide-subdir-nomove + :help "Hide or unhide current directory listing")) + + +;; `Dir' > `Bookmark' menu. +;; +(defvar diredp-bookmark-menu (make-sparse-keymap "Bookmark") + "`Bookmark' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [bookmark] (cons "Bookmark" diredp-bookmark-menu)) + +(define-key diredp-bookmark-menu [diredp-highlight-autofiles-mode] + '(menu-item "Toggle Autofile Highlighting" diredp-highlight-autofiles-mode + :help "Toggle whether to highlight autofile bookmarks" + :visible (and (featurep 'bookmark+) (featurep 'highlight)))) +(define-key diredp-bookmark-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs Here and Below..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-bookmark-menu [bookmark-dired] + '(menu-item "Bookmark Dired Buffer..." bookmark-set :help "Bookmark this Dired buffer")) + + +;; `Dir' > `Navigate' menu. +;; +(defvar diredp-navigate-menu (make-sparse-keymap "Navigate") + "`Navigate' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [navigate] (cons "Navigate" diredp-navigate-menu)) + +(define-key diredp-navigate-menu [insert] + '(menu-item "Move To This Subdir" dired-maybe-insert-subdir + :help "Move to subdirectory line or listing")) +(define-key diredp-navigate-menu [tree-down] + '(menu-item "Tree Down" dired-tree-down :help "Go to first subdirectory header down the tree")) +(define-key diredp-navigate-menu [tree-up] + '(menu-item "Tree Up" dired-tree-up :help "Go to first subdirectory header up the tree")) +(define-key diredp-navigate-menu [up] + '(menu-item "Up Directory" diredp-up-directory :help "Dired the parent directory")) +(define-key diredp-navigate-menu [prev-subdir] + '(menu-item "Prev Subdir" diredp-prev-subdir :help "Go to previous subdirectory header line")) +(define-key diredp-navigate-menu [next-subdir] + '(menu-item "Next Subdir" diredp-next-subdir :help "Go to next subdirectory header line")) +(define-key diredp-navigate-menu [prev-dirline] + '(menu-item "Prev Dirline" diredp-prev-dirline :help "Move to previous directory-file line")) +(define-key diredp-navigate-menu [next-dirline] + '(menu-item "Next Dirline" diredp-next-dirline :help "Move to next directory-file line")) + +(define-key diredp-menu-bar-dir-menu [separator-subdir] '("--")) ; -------------------------- + +(define-key diredp-menu-bar-dir-menu [image-dired-dired-toggle-marked-thumbs] + '(menu-item "Toggle Image Thumbnails" image-dired-dired-toggle-marked-thumbs + :enable (fboundp 'image-dired-dired-toggle-marked-thumbs) + :help "Add or remove image thumbnails in front of marked file names")) +(when (fboundp 'dired-isearch-filenames) ; Emacs 23+ + (define-key diredp-menu-bar-dir-menu [isearch-filenames-regexp] + '(menu-item "Isearch Regexp in File Names..." dired-isearch-filenames-regexp + :help "Incrementally search for regexp in file names only")) + (define-key diredp-menu-bar-dir-menu [isearch-filenames] + '(menu-item "Isearch in File Names..." dired-isearch-filenames + :help "Incrementally search for literal text in file names only."))) +(when (or (> emacs-major-version 21) (fboundp 'wdired-change-to-wdired-mode)) + (define-key diredp-menu-bar-dir-menu [wdired-mode] + '(menu-item "Edit File Names (WDired)" wdired-change-to-wdired-mode + :help "Put a Dired buffer in a mode in which filenames are editable" + :keys "C-x C-q" :filter (lambda (x) (and (derived-mode-p 'dired-mode) x))))) +(define-key diredp-menu-bar-dir-menu [diredp-yank-files] + '(menu-item "Paste Files from Copied Absolute Names" diredp-yank-files + :help "Paste files here whose absolute names you copied" + :enable (catch 'dir-menu--yank-files + (let ((files (car kill-ring-yank-pointer))) + (and (stringp files) + (dolist (file (split-string files)) + (unless (file-name-absolute-p file) (throw 'dir-menu--yank-files nil))))) + t))) +(when (fboundp 'dired-compare-directories) ; Emacs 22+ + (define-key diredp-menu-bar-dir-menu [compare-directories] + '(menu-item "Compare Directories..." dired-compare-directories + :help "Mark files with different attributes in two Dired buffers"))) + +(define-key diredp-menu-bar-dir-menu [separator-dired-on-set] '("--")) ; -------------------- + +(define-key diredp-menu-bar-dir-menu [diredp-dired-recent-dirs] + '(menu-item "Dired Recent Directories..." diredp-dired-recent-dirs + :visible (boundp 'recentf-list) :enable (and (boundp 'recentf-list) (consp recentf-list)) + :help "Open a Dired buffer for recently used directories")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-inserted-subdirs] + '(menu-item "Dired Each Inserted Subdir..." diredp-dired-inserted-subdirs + :enable (cdr dired-subdir-alist) ; First elt is current dir. Must have at least one more. + :help "Open Dired separately for each of the inserted subdirectories")) +(define-key diredp-menu-bar-dir-menu [diredp-add-to-this-dired-buffer] + '(menu-item "Add Entries Here..." diredp-add-to-this-dired-buffer + :help "Add individual file and directory names to the listing" + :keys "C-x E")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-union] + '(menu-item "Dired Union..." diredp-dired-union + :help "Open Dired for the union of some existing Dired buffers")) +(define-key diredp-menu-bar-dir-menu [diredp-fileset-other-window] + '(menu-item "Dired Fileset..." diredp-fileset-other-window + :enable (> emacs-major-version 21) :help "Open Dired on an Emacs fileset")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-for-files] + '(menu-item "Dired Files Located Anywhere" diredp-dired-for-files + :help "Open Dired on specific files whose names you provide")) +(define-key diredp-menu-bar-dir-menu [diredp-marked-other-window] + '(menu-item "Dired Marked Files in Other Window" diredp-marked-other-window + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files only, in other window")) +(define-key diredp-menu-bar-dir-menu [diredp-marked] + '(menu-item "Dired Marked Files" diredp-marked + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files only")) +(define-key diredp-menu-bar-dir-menu [dired] + '(menu-item "Dired (Filter via Wildcards)..." dired + :help "Explore a directory (you can provide wildcards)")) + +(define-key diredp-menu-bar-dir-menu [separator-dired] '("--")) ; --------------------- + +(define-key diredp-menu-bar-dir-menu [insert] + '(menu-item "Insert/Move-To This Subdir" dired-maybe-insert-subdir + :help "Move to subdirectory line or listing")) +(define-key diredp-menu-bar-dir-menu [revert] + '(menu-item "Refresh (Sync \& Show All)" revert-buffer :help "Update directory contents")) +(define-key diredp-menu-bar-dir-menu [create-directory] ; Moved from "Immediate". + '(menu-item "New Directory..." dired-create-directory :help "Create a directory")) + + +;;; Mouse-3 menu binding. +(define-key dired-mode-map [down-mouse-3] 'diredp-mouse-3-menu) +(define-key dired-mode-map [mouse-3] 'ignore) + + +;;; Non-menu Dired bindings. + +;; Move `dired-omit-mode' to `C-x M-o', so prefix key `M-o' is free for face/font-lock stuff. +(define-key dired-mode-map "\C-x\M-o" (if (fboundp 'dired-omit-mode) 'dired-omit-mode 'dired-omit-toggle)) +(when (memq (lookup-key dired-mode-map "\M-o") '(dired-omit-mode dired-omit-toggle)) + (define-key dired-mode-map "\M-o" nil)) + +;; These are global, not just Dired mode. They are on prefix key `C-x D'. +(unless (lookup-key ctl-x-map "D") + (define-key ctl-x-map "D" nil) ; For Emacs 20 + (define-key ctl-x-map "DA" 'diredp-add-to-dired-buffer) ; `C-x D A' + (define-key ctl-x-map "DF" 'diredp-dired-for-files) ; `C-x D F' + (define-key ctl-x-map "DR" 'diredp-dired-recent-dirs) ; `C-x D R' + (define-key ctl-x-map "DS" 'diredp-fileset) ; `C-x D S' + (define-key ctl-x-map "DU" 'diredp-dired-union)) ; `C-x D U' + +(unless (lookup-key ctl-x-4-map "D") + (define-key ctl-x-4-map "D" nil) ; For Emacs 20 + (define-key ctl-x-4-map "DA" 'diredp-add-to-dired-buffer-other-window) ; `C-x 4 D A' + (define-key ctl-x-4-map "DF" 'diredp-dired-for-files-other-window) ; `C-x 4 D F' + (define-key ctl-x-4-map "DR" 'diredp-dired-recent-dirs-other-window) ; `C-x 4 D R' + (define-key ctl-x-4-map "DS" 'diredp-fileset-other-window) ; `C-x 4 D S' + (define-key ctl-x-4-map "DU" 'diredp-dired-union-other-window)) ; `C-x 4 D U' + +;; Navigation +(substitute-key-definition 'dired-up-directory 'diredp-up-directory dired-mode-map) +(substitute-key-definition 'dired-next-line 'diredp-next-line dired-mode-map) +(substitute-key-definition 'dired-previous-line 'diredp-previous-line dired-mode-map) +(substitute-key-definition 'dired-next-dirline 'diredp-next-dirline dired-mode-map) +(substitute-key-definition 'dired-prev-dirline 'diredp-prev-dirline dired-mode-map) +(substitute-key-definition 'dired-next-subdir 'diredp-next-subdir dired-mode-map) +(substitute-key-definition 'dired-prev-subdir 'diredp-prev-subdir dired-mode-map) + + +(define-key dired-mode-map [S-down-mouse-1] 'ignore) ; (normally `mouse-set-font') +;; `diredp-mouse-mark-region-files' provides Windows-Explorer behavior +;; for selecting (marking) files. +(define-key dired-mode-map [S-mouse-1] 'diredp-mouse-mark-region-files) ; `S-mouse-1' +(define-key dired-mode-map [mouse-2] 'dired-mouse-find-file-other-window) ; `mouse-2' +;; But be aware that `dired-sort-menu.el' binds `S-mouse-2' to `dired-sort-menu-popup'. +(define-key dired-mode-map [S-down-mouse-2] 'dired-mouse-find-file) ; `S-mouse-2' +(define-key dired-mode-map [S-mouse-2] 'ignore) +(define-key dired-mode-map [M-mouse-2] 'diredp-mouse-find-file-other-frame) ; `M-mouse-2' + +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(progn + (define-key dired-mode-map [(control return)] 'dired-w32explore) ; `C-RET' + (define-key dired-mode-map [(meta return)] 'dired-w32-browser) ; `M-RET' + (define-key dired-mode-map [mouse-2] 'dired-mouse-w32-browser) ; `mouse-2' + (define-key dired-mode-map (kbd "<C-M-return>") 'dired-multiple-w32-browser))) ; `C-M-RET' + +(when (fboundp 'diredp-w32-drives) + (when (< emacs-major-version 21) (define-key dired-mode-map ":" nil)) ; For Emacs 20 + (define-key dired-mode-map ":/" 'diredp-w32-drives)) ; `:/' + +;; Other keyboard keys +(define-key dired-mode-map "@" 'diredp-do-apply-function) ; `@' +(define-key dired-mode-map "\$" 'diredp-hide-subdir-nomove) ; `$' +(define-key dired-mode-map "\M-$" 'dired-hide-subdir) ; `M-$' +(define-key dired-mode-map "=" 'diredp-ediff) ; `=' +;; This replaces the `dired-x.el' binding of `dired-mark-extension'. +(define-key dired-mode-map "*." 'diredp-mark/unmark-extension) ; `* .' +(define-key dired-mode-map "*B" 'diredp-mark-autofiles) ; `* B' +(define-key dired-mode-map [(control meta ?*)] 'diredp-marked-other-window) ; `C-M-*' +(define-key dired-mode-map "\M-a" 'dired-do-search) ; `M-a' +(define-key dired-mode-map "\M-b" 'diredp-do-bookmark) ; `M-b' +(define-key dired-mode-map "\C-\M-b" 'diredp-set-bookmark-file-bookmark-for-marked) ; `C-M-b' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(control meta shift ?b)] ; `C-M-B' (aka `C-M-S-b') + 'diredp-do-bookmark-in-bookmark-file)) +(define-key dired-mode-map "e" 'diredp-visit-this-file) ; `e' (was `dired-find-file') +(define-key dired-mode-map [C-down] 'diredp-visit-next-file) ; `C-down' (was `forward-paragraph') +(define-key dired-mode-map [C-up] 'diredp-visit-previous-file) ; `C-up' (was `backward-paragraph') +(define-key dired-mode-map "\C-\M-G" 'diredp-do-grep) ; `C-M-G' +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key dired-mode-map "\M-h" 'mkhtml-dired-files)) ; `M-h' +(define-key dired-mode-map "\C-\M-i" 'diredp-dired-inserted-subdirs) ; `C-M-i' +(define-key dired-mode-map "\M-q" (if (< emacs-major-version 21) + 'dired-do-query-replace + 'dired-do-query-replace-regexp)) ; `M-q' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(control meta shift ?r)] ; `C-M-R' (aka `C-M-S-r') + 'diredp-toggle-find-file-reuse-dir)) +(define-key dired-mode-map "U" 'dired-unmark-all-marks) ; `U' +(substitute-key-definition 'describe-mode 'diredp-describe-mode ; `h', `C-h m' + dired-mode-map (current-global-map)) + +;; Tags - same keys as in `*Bookmark List*'. +;; +;; NOTE: If this changes then need to update `dired-sort-menu+.el' to reflect the changes. +;; +(define-key dired-mode-map "T" nil) ; For Emacs 20 +(define-key dired-mode-map "T+" 'diredp-tag-this-file) ; `T +' +(define-key dired-mode-map "T-" 'diredp-untag-this-file) ; `T -' +(define-key dired-mode-map "T0" 'diredp-remove-all-tags-this-file) ; `T 0' +(define-key dired-mode-map "Tc" 'diredp-copy-tags-this-file) ; `T c' +(define-key dired-mode-map "Tp" 'diredp-paste-add-tags-this-file) ; `T p' +(define-key dired-mode-map "Tq" 'diredp-paste-replace-tags-this-file) ; `T q' +(define-key dired-mode-map "Tv" 'diredp-set-tag-value-this-file) ; `T v' +(define-key dired-mode-map "T\M-w" 'diredp-copy-tags-this-file) ; `T M-w' +(define-key dired-mode-map "T\C-y" 'diredp-paste-add-tags-this-file) ; `T C-y' +(define-key dired-mode-map "T>+" 'diredp-do-tag) ; `T > +' +(define-key dired-mode-map "T>-" 'diredp-do-untag) ; `T > -' +(define-key dired-mode-map "T>0" 'diredp-do-remove-all-tags) ; `T > 0' +(define-key dired-mode-map "T>p" 'diredp-do-paste-add-tags) ; `T > p' +(define-key dired-mode-map "T>q" 'diredp-do-paste-replace-tags) ; `T > q' +(define-key dired-mode-map "T>v" 'diredp-do-set-tag-value) ; `T > v' +(define-key dired-mode-map "T>\C-y" 'diredp-do-paste-add-tags) ; `T > C-y' +(define-key dired-mode-map "Tm%" 'diredp-mark-files-tagged-regexp) ; `T m %' +(define-key dired-mode-map "Tm*" 'diredp-mark-files-tagged-all) ; `T m *' +(define-key dired-mode-map "Tm+" 'diredp-mark-files-tagged-some) ; `T m +' +(define-key dired-mode-map "Tm~*" 'diredp-mark-files-tagged-not-all) ; `T m ~ *' +(define-key dired-mode-map "Tm~+" 'diredp-mark-files-tagged-none) ; `T m ~ +' +(define-key dired-mode-map "Tu%" 'diredp-unmark-files-tagged-regexp) ; `T u %' +(define-key dired-mode-map "Tu*" 'diredp-unmark-files-tagged-all) ; `T u *' +(define-key dired-mode-map "Tu+" 'diredp-unmark-files-tagged-some) ; `T u +' +(define-key dired-mode-map "Tu~*" 'diredp-unmark-files-tagged-not-all) ; `T u ~ *' +(define-key dired-mode-map "Tu~+" 'diredp-unmark-files-tagged-none) ; `T u ~ +' +;; $$$$$$ (define-key dired-mode-map [(control ?+)] 'diredp-do-tag) +;; $$$$$$ (define-key dired-mode-map [(control ?-)] 'diredp-do-untag) + + +;; Vanilla Emacs binds `c' to `dired-do-compress-to'. Use `M-z' instead'. +;; (`dired-sort-menu.el' binds `c' to `dired-sort-menu-toggle-ignore-case'.) +;; +(when (fboundp 'dired-do-compress-to) ; Emacs 25+ + (define-key dired-mode-map (kbd "M-z") 'dired-do-compress-to)) + + +;; Commands for operating on the current line's file. When possible, +;; these are lower-case versions of the upper-case commands for operating on +;; the marked files. (Most of the other corresponding lower-case letters are already +;; defined and cannot be used here.) + +;; $$$$$$ (define-key dired-mode-map [(control meta ?+)] 'diredp-tag-this-file) +;; $$$$$$ (define-key dired-mode-map [(control meta ?-)] 'diredp-untag-this-file) +(define-key dired-mode-map "\r" 'dired-find-file) ; `RET' +(when (fboundp 'diredp-describe-file) + (define-key dired-mode-map (kbd "C-h RET") 'diredp-describe-file) ; `C-h RET' + (define-key dired-mode-map (kbd "C-h C-<return>") 'diredp-describe-file)) ; `C-h C-RET' +(define-key dired-mode-map "%c" 'diredp-capitalize) ; `% c' +(define-key dired-mode-map "b" 'diredp-byte-compile-this-file) ; `b' +(define-key dired-mode-map [(control shift ?b)] 'diredp-bookmark-this-file) ; `C-B' +(define-key dired-mode-map "\M-c" 'diredp-capitalize-this-file) ; `M-c' +(when (and (fboundp 'diredp-chgrp-this-file) diredp-bind-problematic-terminal-keys) + (define-key dired-mode-map [(control meta shift ?g)] 'diredp-chgrp-this-file)) ; `C-M-G' (aka `C-M-S-g') +(define-key dired-mode-map "\M-i" 'diredp-insert-subdirs) ; `M-i' +(define-key dired-mode-map "\M-l" 'diredp-downcase-this-file) ; `M-l' +(define-key dired-mode-map "\C-\M-l" 'diredp-list-marked) ; `C-M-l' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(meta shift ?m)] 'diredp-chmod-this-file)) ; `M-M' (aka `M-S-m') +(define-key dired-mode-map "\C-o" 'diredp-find-file-other-frame) ; `C-o' +(when (and (fboundp 'diredp-chown-this-file) diredp-bind-problematic-terminal-keys) + (define-key dired-mode-map [(meta shift ?o)] 'diredp-chown-this-file)) ; `M-O' (aka `M-S-o') +(define-key dired-mode-map "\C-\M-o" 'dired-display-file) ; `C-M-o' (not `C-o') +(define-key dired-mode-map "\M-p" 'diredp-print-this-file) ; `M-p' +(define-key dired-mode-map "r" 'diredp-rename-this-file) ; `r' +(when (fboundp 'image-dired-dired-display-image) + (define-key dired-mode-map "\C-tI" 'diredp-image-show-this-file)) ; `C-t I' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(meta shift ?t)] 'diredp-touch-this-file) ; `M-T' (aka `M-S-t') + (define-key dired-mode-map [(control meta shift ?t)] 'dired-do-touch)) ; `C-M-T' (aka `C-M-S-t') +(define-key dired-mode-map "\M-u" 'diredp-upcase-this-file) ; `M-u' +(define-key dired-mode-map "y" 'diredp-relsymlink-this-file) ; `y' +(define-key dired-mode-map "\C-w" 'diredp-move-files-named-in-kill-ring) ; `C-w' +(define-key dired-mode-map "\C-y" 'diredp-yank-files) ; `C-y' +(define-key dired-mode-map "z" 'diredp-compress-this-file) ; `z' +(when (fboundp 'dired-show-file-type) + (define-key dired-mode-map "_" 'dired-show-file-type)) ; `_' (underscore) +(substitute-key-definition 'kill-line 'diredp-delete-this-file ; `C-k', `delete', `deleteline' + dired-mode-map (current-global-map)) + + +;; Commands that handle marked below, recursively. +;; Use `M-+' as a prefix key for all such commands. + +(define-prefix-command 'diredp-recursive-map) +(define-key dired-mode-map "\M-+" diredp-recursive-map) ; `M-+' + +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "\M-\C-?" 'diredp-unmark-all-files-recursive)) ; `M-DEL' +(define-key diredp-recursive-map "@" 'diredp-do-apply-function-recursive) ; `@' +(define-key diredp-recursive-map "#" 'diredp-flag-auto-save-files-recursive) ; `#' +(define-key diredp-recursive-map "*@" 'diredp-mark-symlinks-recursive) ; `* @' +(define-key diredp-recursive-map "**" 'diredp-mark-executables-recursive) ; `* *' +(define-key diredp-recursive-map "*/" 'diredp-mark-directories-recursive) ; `* /' +(define-key diredp-recursive-map "*." 'diredp-mark-extension-recursive) ; `* .' +(define-key diredp-recursive-map "*(" 'diredp-mark-sexp-recursive) ; `* (' +(define-key diredp-recursive-map "*B" 'diredp-mark-autofiles-recursive) ; `* B' +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "*c" 'diredp-change-marks-recursive)) ; `* c' +(define-key diredp-recursive-map "*%" 'diredp-mark-files-regexp-recursive) ; `* %' +(when (> emacs-major-version 22) + (define-key diredp-recursive-map ":d" 'diredp-do-decrypt-recursive) ; `: d' + (define-key diredp-recursive-map ":e" 'diredp-do-encrypt-recursive) ; `: e' + (define-key diredp-recursive-map ":s" 'diredp-do-sign-recursive) ; `: s' + (define-key diredp-recursive-map ":v" 'diredp-do-verify-recursive)) ; `: v' +(define-key diredp-recursive-map "%c" 'diredp-capitalize-recursive) ; `% c' +(define-key diredp-recursive-map "%g" 'diredp-mark-files-containing-regexp-recursive) ; `% g' +(define-key diredp-recursive-map "%l" 'diredp-downcase-recursive) ; `% l' +(define-key diredp-recursive-map "%m" 'diredp-mark-files-regexp-recursive) ; `% m' +(define-key diredp-recursive-map "%u" 'diredp-upcase-recursive) ; `% u' +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-recursive-map "&" 'diredp-do-async-shell-command-recursive)) ; `&' +(define-key diredp-recursive-map "!" 'diredp-do-shell-command-recursive) ; `!' +(define-key diredp-recursive-map (kbd "C-M-*") 'diredp-marked-recursive-other-window) ; `C-M-*' +(define-key diredp-recursive-map "A" 'diredp-do-search-recursive) ; `A' +(define-key diredp-recursive-map "\M-b" 'diredp-do-bookmark-recursive) ; `M-b' +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(meta shift ?b)] ; `M-B' (aka `M-S-b') + 'diredp-do-bookmark-dirs-recursive)) +(define-key diredp-recursive-map (kbd "C-M-b") ; `C-M-b' + 'diredp-set-bookmark-file-bookmark-for-marked-recursive) +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(control meta shift ?b)] ; `C-M-B' (aka `C-M-S-b') + 'diredp-do-bookmark-in-bookmark-file-recursive)) +(define-key diredp-recursive-map "C" 'diredp-do-copy-recursive) ; `C' +(define-key diredp-recursive-map "D" 'diredp-do-delete-recursive) ; `D' +(define-key diredp-recursive-map "F" 'diredp-do-find-marked-files-recursive) ; `F' +(when (fboundp 'diredp-do-chgrp-recursive) + (define-key diredp-recursive-map "G" 'diredp-do-chgrp-recursive)) ; `G' +(define-key diredp-recursive-map "\C-\M-G" 'diredp-do-grep-recursive) ; `C-M-G' +(define-key diredp-recursive-map "H" 'diredp-do-hardlink-recursive) ; `H' +(define-key diredp-recursive-map "\M-i" 'diredp-insert-subdirs-recursive) ; `M-i' +(define-key diredp-recursive-map "\C-\M-l" 'diredp-list-marked-recursive) ; `C-M-l' +(define-key diredp-recursive-map "M" 'diredp-do-chmod-recursive) ; `M' +(when (fboundp 'diredp-do-chown-recursive) + (define-key diredp-recursive-map "O" 'diredp-do-chown-recursive)) ; `O' +(define-key diredp-recursive-map "P" 'diredp-do-print-recursive) ; `P' +(define-key diredp-recursive-map "Q" 'diredp-do-query-replace-regexp-recursive) ; `Q' +(define-key diredp-recursive-map "R" 'diredp-do-move-recursive) ; `R' +(define-key diredp-recursive-map "S" 'diredp-do-symlink-recursive) ; `S' +(define-key diredp-recursive-map (kbd "M-s a C-s") ; `M-s a C-s' + 'diredp-do-isearch-recursive) +(define-key diredp-recursive-map (kbd "M-s a C-M-s") ; `M-s a C-M-s' + 'diredp-do-isearch-regexp-recursive) +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(control meta shift ?t)] + 'diredp-do-touch-recursive)) ; `C-M-T' (aka `C-M-S-t') +(define-key diredp-recursive-map "\C-tc" 'diredp-image-dired-comment-files-recursive) ; `C-t c' +(define-key diredp-recursive-map "\C-td" 'diredp-image-dired-display-thumbs-recursive) ; `C-t d' +(define-key diredp-recursive-map "\C-tr" 'diredp-image-dired-delete-tag-recursive) ; `C-t r' +(define-key diredp-recursive-map "\C-tt" 'diredp-image-dired-tag-files-recursive) ; `C-t t' +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "U" 'diredp-unmark-all-marks-recursive)) ; `U' +(define-key diredp-recursive-map "\M-(" 'diredp-mark-sexp-recursive) ; `M-(' +(define-key diredp-recursive-map "\M-w" 'diredp-copy-filename-as-kill-recursive) ; `M-w' +(define-key diredp-recursive-map "Y" 'diredp-do-relsymlink-recursive) ; `Y' + +(eval-after-load "w32-browser" + '(define-key diredp-recursive-map (kbd "<C-M-return>") 'diredp-multiple-w32-browser-recursive)) ; `C-M-RET' + +;; Undefine some bindings that would try to modify a Dired buffer. Their key sequences will +;; then appear to the user as available for local (Dired) definition. +(when (fboundp 'undefine-killer-commands) (undefine-killer-commands dired-mode-map)) + +;;;;;;;;;;;; + +(setq diredp-loaded-p t) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; dired+.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el new file mode 100644 index 000000000000..8fd3c9425866 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el @@ -0,0 +1,304 @@ +;;; reason-indent.el --- Indentation functions for ReasonML -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Indentation functions for Reason. + +;;; Code: + +(defconst reason-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") + +(defcustom reason-indent-offset 2 + "Indent Reason code by this number of spaces." + :type 'integer + :group 'reason-mode + :safe #'integerp) + +(defun reason-looking-back-str (str) + "Like `looking-back' but for fixed strings rather than regexps. +Works around some regexp slowness. +Argument STR string to search for." + (let ((len (length str))) + (and (> (point) len) + (equal str (buffer-substring-no-properties (- (point) len) (point)))))) + +(defun reason-paren-level () + "Get the level of nesting inside parentheses." + (nth 0 (syntax-ppss))) + +(defun reason-in-str-or-cmnt () + "Return whether point is currently inside a string or a comment." + (nth 8 (syntax-ppss))) + +(defun reason-rewind-past-str-cmnt () + "Rewind past string or comment." + (goto-char (nth 8 (syntax-ppss)))) + +(defun reason-rewind-irrelevant () + "Rewind past irrelevant characters (whitespace of inside comments)." + (interactive) + (let ((starting (point))) + (skip-chars-backward "[:space:]\n") + (if (reason-looking-back-str "*/") (backward-char)) + (if (reason-in-str-or-cmnt) + (reason-rewind-past-str-cmnt)) + (if (/= starting (point)) + (reason-rewind-irrelevant)))) + +(defun reason-align-to-expr-after-brace () + "Align the expression at point to the expression after the previous brace." + (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (when (looking-at "[[:space:]]") + (forward-word 1) + (backward-word 1)) + (current-column)))) + +(defun reason-align-to-prev-expr () + "Align the expression at point to the previous expression." + (let ((alignment (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (if (looking-at "[[:space:]]") + (progn + (forward-word 1) + (backward-word 1)) + (backward-char)) + (current-column))))) + (if (not alignment) + (save-excursion + (forward-char) + (forward-line) + (back-to-indentation) + (current-column)) + alignment))) + +;;; Start of a reason binding +(defvar reason-binding + (regexp-opt '("let" "type" "module" "fun"))) + +(defun reason-beginning-of-defun (&optional arg) + "Move backward to the beginning of the current defun. + +With ARG, move backward multiple defuns. Negative ARG means +move forward. + +This is written mainly to be used as `beginning-of-defun-function'. +Don't move to the beginning of the line. `beginning-of-defun', +which calls this, does that afterwards." + (interactive "p") + (re-search-backward (concat "^\\(" reason-binding "\\)\\_>") + nil 'move (or arg 1))) + +(defun reason-end-of-defun () + "Move forward to the next end of defun. + +With argument, do it that many times. +Negative argument -N means move back to Nth preceding end of defun. + +Assume that this is called after โbeginning-of-defunโ. So point is +at the beginning of the defun body. + +This is written mainly to be used as `end-of-defun-function' for Reason." + (interactive) + ;; Find the opening brace + (if (re-search-forward "[{]" nil t) + (progn + (goto-char (match-beginning 0)) + ;; Go to the closing brace + (condition-case nil + (forward-sexp) + (scan-error + ;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer + (goto-char (point-max))))) + ;; There is no opening brace, so consider the whole buffer to be one "defun" + (goto-char (point-max)))) + +(defun reason-rewind-to-beginning-of-current-level-expr () + "Rewind to the beginning of the expression on the current level of nesting." + (interactive) + (let ((current-level (reason-paren-level))) + (back-to-indentation) + (when (looking-at "=>") + (reason-rewind-irrelevant) + (back-to-indentation)) + (while (> (reason-paren-level) current-level) + (backward-up-list) + (back-to-indentation)))) + +(defun reason-mode-indent-line () + "Indent current line." + (interactive) + (let ((indent + (save-excursion + (back-to-indentation) + ;; Point is now at beginning of current line + (let* ((level (reason-paren-level)) + (baseline + ;; Our "baseline" is one level out from the indentation of the expression + ;; containing the innermost enclosing opening bracket. That + ;; way if we are within a block that has a different + ;; indentation than this mode would give it, we still indent + ;; the inside of it correctly relative to the outside. + (if (= 0 level) + 0 + (save-excursion + (reason-rewind-irrelevant) + (if (save-excursion + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "<")) + (progn + (reason-rewind-to-beginning-of-current-level-expr) + (current-column)) + (progn + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + + (cond + ((looking-at "switch") + (current-column)) + + ((looking-at "|") + (+ (current-column) (* reason-indent-offset 2))) + + (t + (let ((current-level (reason-paren-level))) + (save-excursion + (while (and (= current-level (reason-paren-level)) + (not (looking-at reason-binding))) + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr)) + (+ (current-column) reason-indent-offset))))))))))) + (cond + ;; A function return type is indented to the corresponding function arguments + ((looking-at "=>") + (+ baseline reason-indent-offset)) + + ((reason-in-str-or-cmnt) + (cond + ;; In the end of the block -- align with star + ((looking-at "*/") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; * asdf + ;; */ + ;; + ((looking-at "*") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; asdf + ;; */ + ;; + (t (+ baseline (+ reason-indent-offset 1))))) + + ((looking-at "</") (- baseline reason-indent-offset)) + + ;; A closing brace is 1 level unindented + ((looking-at "}\\|)\\|\\]") + (save-excursion + (reason-rewind-irrelevant) + (let ((jsx? (reason-looking-back-str ">"))) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (cond + ((looking-at "switch") baseline) + + (jsx? (current-column)) + + (t (- baseline reason-indent-offset)))))) + + ;; Doc comments in /** style with leading * indent to line up the *s + ((and (nth 4 (syntax-ppss)) (looking-at "*")) + (+ 1 baseline)) + + ;; If we're in any other token-tree / sexp, then: + (t + (or + ;; If we are inside a pair of braces, with something after the + ;; open brace on the same line and ending with a comma, treat + ;; it as fields and align them. + (when (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + ;; Point is now at the beginning of the containing set of braces + (reason-align-to-expr-after-brace))) + + (progn + (back-to-indentation) + (cond ((looking-at (regexp-opt '("and" "type"))) + baseline) + ((save-excursion + (reason-rewind-irrelevant) + (= (point) 1)) + baseline) + ((save-excursion + (while (looking-at "|") + (reason-rewind-irrelevant) + (back-to-indentation)) + (looking-at (regexp-opt '("type")))) + (+ baseline reason-indent-offset)) + ((looking-at "|\\|/[/*]") + baseline) + ((and (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "switch"))) + (+ baseline reason-indent-offset)) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "[{;,\\[(]" (- (point) 2))) + baseline) + ((and + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + (and (looking-at reason-binding) + (not (progn + (forward-sexp) + (forward-sexp) + (skip-chars-forward "[:space:]\n") + (looking-at "="))))) + (not (save-excursion + (skip-chars-backward "[:space:]\n") + (reason-looking-back-str "=>")))) + (save-excursion + (reason-rewind-irrelevant) + (backward-sexp) + (reason-align-to-prev-expr))) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "<\/.*?>" (- (point) 30))) + baseline) + (t + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + + (if (looking-at "|") + baseline + (+ baseline reason-indent-offset))))) + ;; Point is now at the beginning of the current line + )))))))) + + (when indent + ;; If we're at the beginning of the line (before or at the current + ;; indentation), jump with the indentation change. Otherwise, save the + ;; excursion so that adding the indentations will leave us at the + ;; equivalent position within the line to where we were before. + (if (<= (current-column) (current-indentation)) + (indent-line-to indent) + (save-excursion (indent-line-to indent)))))) + +(provide 'reason-indent) + +;;; reason-indent.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el new file mode 100644 index 000000000000..6ceaed1e9340 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el @@ -0,0 +1,216 @@ +;;; reason-interaction.el --- Phrase navitagion for rtop -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Phrase navigation for utop and maybe other REPLs. + +;; The utop compatibility layer for Reason was mainly taken from: +;; https://github.com/ocaml/tuareg/blob/master/tuareg-light.el (big thanks!) + +;;; Code: + +(defun reason-backward-char (&optional step) + "Go back one char. +Similar to `backward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (- (point) step)) + (goto-char (1- (point))))) + +(defun reason-forward-char (&optional step) + "Go forward one char. +Similar to `forward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (+ (point) step)) + (goto-char (1+ (point))))) + +(defun reason-in-literal-p () + "Return non-nil if point is inside an Reason literal." + (nth 3 (syntax-ppss))) + +(defconst reason-comment-delimiter-regexp "\\*/\\|/\\*" + "Regex for identify either open or close comment delimiters.") + +(defun reason-in-between-comment-chars-p () + "Return non-nil iff point is in between the comment delimiter chars. +It returns non-nil if point is between the chars only (*|/ or /|* +where | is point)." + (and (not (bobp)) (not (eobp)) + (or (and (char-equal ?/ (char-before)) (char-equal ?* (char-after))) + (and (char-equal ?* (char-before)) (char-equal ?/ (char-after)))))) + +(defun reason-looking-at-comment-delimiters-p () + "Return non-nil iff point in between comment delimiters." + (looking-at-p reason-comment-delimiter-regexp)) + +(defun reason-in-between-comment-delimiters-p () + "Return non-nil if inside /* and */." + (nth 4 (syntax-ppss))) + +(defun reason-in-comment-p () + "Return non-nil iff point is inside or right before a comment." + (or (reason-in-between-comment-delimiters-p) + (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p))) + +(defun reason-beginning-of-literal-or-comment () + "Skip to the beginning of the current literal or comment (or buffer)." + (interactive) + (goto-char (or (nth 8 (syntax-ppss)) (point)))) + +(defun reason-inside-block-scope-p () + "Skip to the beginning of the current literal or comment (or buffer)." + (and (> (nth 0 (syntax-ppss)) 0) + (let ((delim-start (nth 1 (syntax-ppss)))) + (save-excursion + (goto-char delim-start) + (char-equal ?{ (following-char)))))) + +(defun reason-at-phrase-break-p () + "Is the underlying `;' a phrase break?" + ;; Difference from OCaml, the phrase separator is a single semi-colon + (and (not (eobp)) + (char-equal ?\; (following-char)))) + +(defun reason-skip-to-close-delimiter (&optional limit) + "Skip to the end of a Reason block. +It basically calls `re-search-forward` in order to go to any +closing delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-forward` directly." + (re-search-forward "\\s)" limit 'move)) + +(defun reason-skip-back-to-open-delimiter (&optional limit) + "Skip to the beginning of a Reason block backwards. +It basically calls `re-search-backward` in order to go to any +opening delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-backward` directly." + (re-search-backward "\\s(" limit 'move)) + +(defun reason-find-phrase-end () + "Skip to the end of a phrase." + (while (and (not (eobp)) + (not (reason-at-phrase-break-p))) + (if (re-search-forward ";" nil 'move) + (progn (when (reason-inside-block-scope-p) + (reason-skip-to-close-delimiter)) + (goto-char (1- (point)))) + ;; avoid infinite loop at the end of the buffer + (re-search-forward "[[:space:]\\|\n]+" nil 'move))) + (min (goto-char (1+ (point))) (point-max))) + +(defun reason-skip-blank-and-comments () + "Skip blank spaces and comments." + (cond + ((eobp) (point)) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-forward "*/" nil t) + (reason-skip-blank-and-comments))) + ((eolp) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + (t (progn (skip-syntax-forward " ") + (point))))) + +(defun reason-skip-back-blank-and-comments () + "Skip blank spaces and comments backwards." + (cond + ((bobp) (point)) + ((looking-back reason-comment-delimiter-regexp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-backward "/*" nil t) + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((bolp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + (t (progn (skip-syntax-backward " ") + (point))))) + +(defun reason-ro (&rest words) + "Build a regex matching iff at least a word in WORDS is present." + (concat "\\<" (regexp-opt words t) "\\>")) + +(defconst reason-find-phrase-beginning-regexp + (concat (reason-ro "end" "type" "module" "sig" "struct" "class" + "exception" "open" "let") + "\\|^#[ \t]*[a-z][_a-z]*\\>\\|;")) + +(defun reason-at-phrase-start-p () + "Return t if is looking at the beginning of a phrase. +A phrase starts when a toplevel keyword is at the beginning of a line." + (or (looking-at "#") + (looking-at reason-find-phrase-beginning-regexp))) + +(defun reason-find-phrase-beginning-backward () + "Find the beginning of a phrase and return point. +It scans code backwards, therefore the caller can assume that the +beginning of the phrase (if found) is always before the starting +point. No error is signalled and (point-min) is returned when a +phrease cannot be found." + (beginning-of-line) + (while (and (not (bobp)) (not (reason-at-phrase-start-p))) + (if (reason-inside-block-scope-p) + (reason-skip-back-to-open-delimiter) + (re-search-backward reason-find-phrase-beginning-regexp nil 'move))) + (point)) + +(defun reason-discover-phrase () + "Discover a Reason phrase in the buffer." + ;; TODO reason-with-internal-syntax ;; tuareg2 modifies the syntax table (removed for now) + ;; TODO stop-at-and feature for phrase detection (do we need it?) + ;; TODO tuareg2 has some custom logic for module and class (do we need it?) + (save-excursion + (let ((case-fold-search nil)) + (reason-skip-blank-and-comments) + (list (reason-find-phrase-beginning-backward) ;; beginning + (reason-find-phrase-end) ;; end + (save-excursion ;; end-with-comment + (reason-skip-blank-and-comments) + (point)))))) + +(defun reason-discover-phrase-debug () + "Discover a Reason phrase in the buffer (debug mode)." + (let ((triple (reason-discover-phrase))) + (message (concat "Evaluating: \"" (reason-fetch-phrase triple) "\"")) + triple)) + +(defun reason-fetch-phrase (triple) + "Fetch the phrase text given a TRIPLE." + (let* ((start (nth 0 triple)) + (end (nth 1 triple))) ;; we don't need end-with-comment + (buffer-substring-no-properties start end))) + +(defun reason-next-phrase () + "Skip to the beginning of the next phrase." + (cond + ((reason-at-phrase-start-p) (point)) + ((eolp) (progn + (forward-char 1) + (reason-skip-blank-and-comments) + (reason-next-phrase))) + ((reason-inside-block-scope-p) (progn (reason-skip-to-close-delimiter) + (reason-next-phrase))) + ((looking-at ";") (progn + (forward-char 1) + (reason-next-phrase))) + (t (progn (end-of-line) + (reason-next-phrase))))) + +(provide 'reason-interaction) + +;;; reason-interaction.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el new file mode 100644 index 000000000000..789735955db2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el @@ -0,0 +1,242 @@ +;;; reason-mode.el --- A major mode for editing ReasonML -*-lexical-binding: t-*- +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Version: 0.4.0 +;; Author: Mozilla +;; Url: https://github.com/reasonml-editor/reason-mode +;; Keywords: languages, ocaml +;; Package-Requires: ((emacs "24.3")) + +;; This file is NOT part of GNU Emacs. + +;; This file is distributed under the terms of both the MIT license and the +;; Apache License (version 2.0). + +;;; Commentary: +;; This project provides useful functions and helpers for developing code +;; using the Reason programming language (https://facebook.github.io/reason). +;; +;; Reason is an umbrella project that provides a curated layer for OCaml. +;; +;; It offers: +;; - A new, familiar syntax for the battle-tested language that is OCaml. +;; - A workflow for compiling to JavaScript and native code. +;; - A set of friendly documentations, libraries and utilities. +;; +;; See the README.md for more details. + +;;; Code: + +(require 'reason-indent) +(require 'refmt) +(require 'reason-interaction) + +(eval-when-compile (require 'rx) + (require 'compile) + (require 'url-vars)) + +;; Syntax definitions and helpers +(defvar reason-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Operators + (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@)) + (modify-syntax-entry i "." table)) + + ;; Strings + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?\\ "\\" table) + (modify-syntax-entry ?\' "_" table) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" table) + (modify-syntax-entry ?* ". 23n" table) + (modify-syntax-entry ?\n "> b" table) + (modify-syntax-entry ?\^m "> b" table) + + table)) + +(defgroup reason nil + "Support for Reason code." + :link '(url-link "http://facebook.github.io/reason/") + :group 'languages) + +(defcustom reason-mode-hook nil + "Hook called by `reason-mode'." + :type 'hook + :group 'reason) + +;; Font-locking definitions and helpers +(defconst reason-mode-keywords + '("and" "as" + "else" "external" + "fun" "for" + "if" "impl" "in" "include" + "let" + "module" "match" "mod" "move" "mutable" + "open" + "priv" "pub" + "rec" "ref" "return" + "self" "static" "switch" "struct" "super" + "trait" "type" + "use" + "virtual" + "where" "when" "while")) + +(defconst reason-mode-consts + '("true" "false")) + +(defconst reason-special-types + '("int" "float" "string" "char" + "bool" "unit" "list" "array" "exn" + "option" "ref")) + +(defconst reason-camel-case + (rx symbol-start + (group upper (0+ (any word nonascii digit "_"))) + symbol-end)) + +(eval-and-compile + (defconst reason--char-literal-rx + (rx (seq (group "'") + (or (seq "\\" anything) + (not (any "'\\"))) + (group "'"))))) + +(defun reason-re-word (inner) + "Build a word regexp given INNER." + (concat "\\<" inner "\\>")) + +(defun reason-re-grab (inner) + "Build a grab regexp given INNER." + (concat "\\(" inner "\\)")) + +(defun reason-regexp-opt-symbols (words) + "Like `(regexp-opt words 'symbols)`, but will work on Emacs 23. +See rust-mode PR #42. +Argument WORDS argument to pass to `regexp-opt`." + (concat "\\_<" (regexp-opt words t) "\\_>")) + +;;; Syntax highlighting for Reason +(defvar reason-font-lock-keywords + `((,(reason-regexp-opt-symbols reason-mode-keywords) . font-lock-keyword-face) + (,(reason-regexp-opt-symbols reason-special-types) . font-lock-builtin-face) + (,(reason-regexp-opt-symbols reason-mode-consts) . font-lock-constant-face) + + (,reason-camel-case 1 font-lock-type-face) + + ;; Field names like `foo:`, highlight excluding the : + (,(concat (reason-re-grab reason-re-ident) ":[^:]") 1 font-lock-variable-name-face) + ;; Module names like `foo::`, highlight including the :: + (,(reason-re-grab (concat reason-re-ident "::")) 1 font-lock-type-face) + ;; Name punned labeled args like ::foo + (,(concat "[[:space:]]+" (reason-re-grab (concat "::" reason-re-ident))) 1 font-lock-type-face) + + ;; TODO jsx attribs? + (, + (concat "<[/]?" (reason-re-grab reason-re-ident) "[^>]*" ">") + 1 font-lock-type-face))) + +(defun reason-mode-try-find-alternate-file (mod-name extension) + "Switch to the file given by MOD-NAME and EXTENSION." + (let* ((filename (concat mod-name extension)) + (buffer (get-file-buffer filename))) + (if buffer (switch-to-buffer buffer) + (find-file filename)))) + +(defun reason-mode-find-alternate-file () + "Switch to implementation/interface file." + (interactive) + (let ((name buffer-file-name)) + (when (string-match "\\`\\(.*\\)\\.re\\([il]\\)?\\'" name) + (let ((mod-name (match-string 1 name)) + (e (match-string 2 name))) + (cond + ((string= e "i") + (reason-mode-try-find-alternate-file mod-name ".re")) + (t + (reason-mode-try-find-alternate-file mod-name ".rei"))))))) + +(defun reason--syntax-propertize-multiline-string (end) + "Propertize Reason multiline string. +Argument END marks the end of the string." + (let ((ppss (syntax-ppss))) + (when (eq t (nth 3 ppss)) + (let ((key (save-excursion + (goto-char (nth 8 ppss)) + (and (looking-at "{\\([a-z]*\\)|") + (match-string 1))))) + (when (search-forward (format "|%s}" key) end 'move) + (put-text-property (1- (match-end 0)) (match-end 0) + 'syntax-table (string-to-syntax "|"))))))) + +(defun reason-syntax-propertize-function (start end) + "Propertize Reason function. +Argument START marks the beginning of the function. +Argument END marks the end of the function." + (goto-char start) + (reason--syntax-propertize-multiline-string end) + (funcall + (syntax-propertize-rules + (reason--char-literal-rx (1 "\"") (2 "\"")) + ;; multi line strings + ("\\({\\)[a-z]*|" + (1 (prog1 "|" + (goto-char (match-end 0)) + (reason--syntax-propertize-multiline-string end))))) + (point) end)) + +(defvar reason-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-a" #'reason-mode-find-alternate-file) + (define-key map "\C-c\C-r" #'refmt-region-ocaml-to-reason) + (define-key map "\C-c\C-o" #'refmt-region-reason-to-ocaml) + map)) + +;;;###autoload +(define-derived-mode reason-mode prog-mode "Reason" + "Major mode for Reason code. + +\\{reason-mode-map}" + :group 'reason + :syntax-table reason-mode-syntax-table + :keymap reason-mode-map + + ;; Syntax + (setq-local syntax-propertize-function #'reason-syntax-propertize-function) + ;; Indentation + (setq-local indent-line-function 'reason-mode-indent-line) + ;; Fonts + (setq-local font-lock-defaults '(reason-font-lock-keywords)) + ;; Misc + (setq-local comment-start "/*") + (setq-local comment-end "*/") + (setq-local indent-tabs-mode nil) + ;; Allow paragraph fills for comments + (setq-local comment-start-skip "/\\*+[ \t]*") + (setq-local paragraph-start + (concat "^[ \t]*$\\|\\*)$\\|" page-delimiter)) + (setq-local paragraph-separate paragraph-start) + (setq-local require-final-newline t) + (setq-local normal-auto-fill-function nil) + (setq-local comment-multi-line t) + + (setq-local beginning-of-defun-function 'reason-beginning-of-defun) + (setq-local end-of-defun-function 'reason-end-of-defun) + (setq-local parse-sexp-lookup-properties t)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.rei?\\'" . reason-mode)) + +(defun reason-mode-reload () + "Reload Reason mode." + (interactive) + (unload-feature 'reason-mode) + (unload-feature 'reason-indent) + (unload-feature 'reason-interaction) + (require 'reason-mode) + (reason-mode)) + +(provide 'reason-mode) + +;;; reason-mode.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/refmt.el b/users/wpcarro/emacs/.emacs.d/vendor/refmt.el new file mode 100644 index 000000000000..b9ea2b43f0ce --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/refmt.el @@ -0,0 +1,231 @@ +;;; refmt.el --- utility functions to format reason code + +;; Copyright (c) 2014 The go-mode Authors. All rights reserved. +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: + +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following disclaimer +;; in the documentation and/or other materials provided with the +;; distribution. +;; * Neither the name of the copyright holder nor the names of its +;; contributors may be used to endorse or promote products derived from +;; this software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.) + +;;; Commentary: +;; + +;;; Code: + +(require 'cl-lib) + +(defcustom refmt-command "refmt" + "The 'refmt' command." + :type 'string + :group 're-fmt) + +(defcustom refmt-show-errors 'buffer + "Where to display refmt error output. +It can either be displayed in its own buffer, in the echo area, or not at all. +Please note that Emacs outputs to the echo area when writing +files and will overwrite refmt's echo output if used from inside +a `before-save-hook'." + :type '(choice + (const :tag "Own buffer" buffer) + (const :tag "Echo area" echo) + (const :tag "None" nil)) + :group 're-fmt) + +(defcustom refmt-width-mode nil + "Specify width when formatting buffer contents." + :type '(choice + (const :tag "Window width" window) + (const :tag "Fill column" fill) + (const :tag "None" nil)) + :group 're-fmt) + +;;;###autoload +(defun refmt-before-save () + "Add this to .emacs to run refmt on the current buffer when saving: + (add-hook 'before-save-hook 'refmt-before-save)." + (interactive) + (when (eq major-mode 'reason-mode) (refmt))) + +(defun reason--goto-line (line) + (goto-char (point-min)) + (forward-line (1- line))) + +(defun reason--delete-whole-line (&optional arg) + "Delete the current line without putting it in the `kill-ring'. +Derived from function `kill-whole-line'. ARG is defined as for that +function." + (setq arg (or arg 1)) + (if (and (> arg 0) + (eobp) + (save-excursion (forward-visible-line 0) (eobp))) + (signal 'end-of-buffer nil)) + (if (and (< arg 0) + (bobp) + (save-excursion (end-of-visible-line) (bobp))) + (signal 'beginning-of-buffer nil)) + (cond ((zerop arg) + (delete-region (progn (forward-visible-line 0) (point)) + (progn (end-of-visible-line) (point)))) + ((< arg 0) + (delete-region (progn (end-of-visible-line) (point)) + (progn (forward-visible-line (1+ arg)) + (unless (bobp) + (backward-char)) + (point)))) + (t + (delete-region (progn (forward-visible-line 0) (point)) + (progn (forward-visible-line arg) (point)))))) + +(defun reason--apply-rcs-patch (patch-buffer &optional start-pos) + "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer." + (setq start-pos (or start-pos (point-min))) + (let ((first-line (line-number-at-pos start-pos)) + (target-buffer (current-buffer)) + ;; Relative offset between buffer line numbers and line numbers + ;; in patch. + ;; + ;; Line numbers in the patch are based on the source file, so + ;; we have to keep an offset when making changes to the + ;; buffer. + ;; + ;; Appending lines decrements the offset (possibly making it + ;; negative), deleting lines increments it. This order + ;; simplifies the forward-line invocations. + (line-offset 0)) + (save-excursion + (with-current-buffer patch-buffer + (goto-char (point-min)) + (while (not (eobp)) + (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)") + (error "invalid rcs patch or internal error in reason--apply-rcs-patch")) + (forward-line) + (let ((action (match-string 1)) + (from (string-to-number (match-string 2))) + (len (string-to-number (match-string 3)))) + (cond + ((equal action "a") + (let ((start (point))) + (forward-line len) + (let ((text (buffer-substring start (point)))) + (with-current-buffer target-buffer + (cl-decf line-offset len) + (goto-char start-pos) + (forward-line (- from len line-offset)) + (insert text))))) + ((equal action "d") + (with-current-buffer target-buffer + (reason--goto-line (- (1- (+ first-line from)) line-offset)) + (cl-incf line-offset len) + (reason--delete-whole-line len))) + (t + (error "invalid rcs patch or internal error in reason--apply-rcs-patch"))))))))) + +(defun refmt--process-errors (filename tmpfile errorfile errbuf) + (with-current-buffer errbuf + (if (eq refmt-show-errors 'echo) + (progn + (message "%s" (buffer-string)) + (refmt--kill-error-buffer errbuf)) + (insert-file-contents errorfile nil nil nil) + ;; Convert the refmt stderr to something understood by the compilation mode. + (goto-char (point-min)) + (insert "refmt errors:\n") + (while (search-forward-regexp (regexp-quote tmpfile) nil t) + (replace-match (file-name-nondirectory filename))) + (compilation-mode) + (display-buffer errbuf)))) + +(defun refmt--kill-error-buffer (errbuf) + (let ((win (get-buffer-window errbuf))) + (if win + (quit-window t win) + (with-current-buffer errbuf + (erase-buffer)) + (kill-buffer errbuf)))) + +(defun apply-refmt (&optional start end from to) + (setq start (or start (point-min)) + end (or end (point-max)) + from (or from "re") + to (or to "re")) + (let* ((ext (file-name-extension buffer-file-name t)) + (bufferfile (make-temp-file "refmt" nil ext)) + (outputfile (make-temp-file "refmt" nil ext)) + (errorfile (make-temp-file "refmt" nil ext)) + (errbuf (if refmt-show-errors (get-buffer-create "*Refmt Errors*"))) + (patchbuf (get-buffer-create "*Refmt patch*")) + (coding-system-for-read 'utf-8) + (coding-system-for-write 'utf-8) + (width-args + (cond + ((equal refmt-width-mode 'window) + (list "--print-width" (number-to-string (window-body-width)))) + ((equal refmt-width-mode 'fill) + (list "--print-width" (number-to-string fill-column))) + (t + '())))) + (unwind-protect + (save-restriction + (widen) + (write-region start end bufferfile) + (if errbuf + (with-current-buffer errbuf + (setq buffer-read-only nil) + (erase-buffer))) + (with-current-buffer patchbuf + (erase-buffer)) + (if (zerop (apply 'call-process + refmt-command nil (list (list :file outputfile) errorfile) + nil (append width-args (list "--parse" from "--print" to bufferfile)))) + (progn + (call-process-region start end "diff" nil patchbuf nil "-n" "-" + outputfile) + (reason--apply-rcs-patch patchbuf start) + (message "Applied refmt") + (if errbuf (refmt--kill-error-buffer errbuf))) + (message "Could not apply refmt") + (if errbuf + (refmt--process-errors (buffer-file-name) bufferfile errorfile errbuf))))) + (kill-buffer patchbuf) + (delete-file errorfile) + (delete-file bufferfile) + (delete-file outputfile))) + +(defun refmt () + "Format the current buffer according to the refmt tool." + (interactive) + (apply-refmt)) + +(defun refmt-region-ocaml-to-reason (start end) + (interactive "r") + (apply-refmt start end "ml")) + +(defun refmt-region-reason-to-ocaml (start end) + (interactive "r") + (apply-refmt start end "re" "ml")) + +(provide 'refmt) + +;;; refmt.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/>.el b/users/wpcarro/emacs/.emacs.d/wpc/>.el new file mode 100644 index 000000000000..68d8576b8079 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/>.el @@ -0,0 +1,29 @@ +;;; >.el --- Small utility functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Originally I stored the `>>` macro in macros.el, but after setting up linting +;; for my Elisp in CI, `>>` failed because it didn't have the `macros-` +;; namespace. I created this module to establish a `>-` namespace under which I +;; can store some utilities that would be best kept without a cumbersome +;; namespace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro >-> (&rest forms) + "Compose a new, point-free function by composing FORMS together." + (let ((sym (gensym))) + `(lambda (,sym) + (->> ,sym ,@forms)))) + + +(provide '>) +;;; >.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/al.el b/users/wpcarro/emacs/.emacs.d/wpc/al.el new file mode 100644 index 000000000000..e29f853f8ea5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/al.el @@ -0,0 +1,255 @@ +;;; al.el --- Interface for working with associative lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Firstly, a rant: +;; In most cases, I find Elisp's APIs to be confusing. There's a mixture of +;; overloaded functions that leak the implementation details (TODO: provide an +;; example of this.) of the abstract data type, which I find privileges those +;; "insiders" who spend disproportionately large amounts of time in Elisp land, +;; and other functions with little-to-no pattern about the order in which +;; arguments should be applied. In theory, however, most of these APIs could +;; and should be much simpler. This module represents a step in that direction. +;; +;; I'm modelling these APIs after Elixir's APIs. +;; +;; On my wishlist is to create protocols that will allow generic interfaces like +;; Enum protocols, etc. Would be nice to abstract over... +;; - associative lists (i.e. alists) +;; - property lists (i.e. plists) +;; - hash tables +;; ...with some dictionary or map-like interface. This will probably end up +;; being quite similar to the kv.el project but with differences at the API +;; layer. +;; +;; Similar libraries: +;; - map.el: Comes bundled with recent versions of Emacs. +;; - asoc.el: Helpers for working with alists. asoc.el is similar to alist.el +;; because it uses the "!" convention for signalling that a function mutates +;; the underlying data structure. +;; - ht.el: Hash table library. +;; - kv.el: Library for dealing with key-value collections. Note that map.el +;; has a similar typeclass because it works with lists, hash-tables, or +;; arrays. +;; - a.el: Clojure-inspired way of working with key-value data structures in +;; Elisp. Works with alists, hash-tables, and sometimes vectors. +;; +;; Some API design principles: +;; - The "noun" (i.e. alist) of the "verb" (i.e. function) comes last to improve +;; composability with the threading macro (i.e. `->>') and to improve consumers' +;; intuition with the APIs. Learn this once, know it always. +;; +;; - Every function avoids mutating the alist unless it ends with !. +;; +;; - CRUD operations will be named according to the following table: +;; - "create" *and* "set" +;; - "read" *and* "get" +;; - "update" +;; - "delete" *and* "remove" +;; +;; For better or worse, all of this code expects alists in the form of: +;; ((first-name . "William") (last-name . "Carroll")) +;; +;; Special thanks to github.com/alphapapa/emacs-package-dev-handbook for some of +;; the idiomatic ways to update alists. +;; +;; TODO: Include a section that compares alist.el to a.el from +;; github.com/plexus/a.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'dash) +(require 'tuple) +(require 'maybe) + +;; TODO: Support function aliases for: +;; - create/set +;; - read/get +;; - update +;; - delete/remove + +;; Support mutative variants of functions with an ! appendage to their name. + +;; Ensure that the same message about only updating the first occurrence of a +;; key is consistent throughout documentation using string interpolation or some +;; other mechanism. + +;; TODO: Consider wrapping all of this with `(cl-defstruct alist xs)'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst al-enable-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a variadic version of this to easily construct alists. +(defun al-new () + "Return a new, empty alist." + '()) + +;; Create +;; TODO: See if this mutates. +(defun al-set (k v xs) + "Set K to V in XS." + (if (al-has-key? k xs) + (progn + ;; Note: this is intentional `alist-get' and not `al-get'. + (setf (alist-get k xs) v) + xs) + (list-cons `(,k . ,v) xs))) + +(defun al-set! (k v xs) + "Set K to V in XS mutatively. +Note that this doesn't append to the alist in the way that most alists handle + writing. If the k already exists in XS, it is overwritten." + (map-delete xs k) + (map-put! xs k v)) + +;; Read +(defun al-get (k xs) + "Return the value at K in XS; otherwise, return nil. +Returns the first occurrence of K in XS since alists support multiple entries." + (cdr (assoc k xs))) + +(defun al-get-entry (k xs) + "Return the first key-value pair at K in XS." + (assoc k xs)) + +;; Update +;; TODO: Add warning about only the first occurrence being updated in the +;; documentation. +(defun al-update (k f xs) + "Apply F to the value stored at K in XS. +If `K' is not in `XS', this function errors. Use `al-upsert' if you're +interested in inserting a value when a key doesn't already exist." + (if (not (al-has-key? k xs)) + (error "Refusing to update: key does not exist in alist") + (al-set k (funcall f (al-get k xs)) xs))) + +(defun al-update! (k f xs) + "Call F on the entry at K in XS. +Mutative variant of `al-update'." + (al-set! k (funcall f (al-get k xs))xs)) + +;; TODO: Support this. +(defun al-upsert (k v f xs) + "If K exists in `XS' call `F' on the value otherwise insert `V'." + (if (al-has-key? k xs) + (al-update k f xs) + (al-set k v xs))) + +;; Delete +;; TODO: Make sure `delete' and `remove' behave as advertised in the Elisp docs. +(defun al-delete (k xs) + "Deletes the entry of K from XS. +This only removes the first occurrence of K, since alists support multiple + key-value entries. See `al-delete-all' and `al-dedupe'." + (remove (assoc k xs) xs)) + +(defun al-delete! (k xs) + "Delete the entry of K from XS. +Mutative variant of `al-delete'." + (delete (assoc k xs) xs)) + +;; Additions to the CRUD API +;; TODO: Implement this function. +(defun al-dedupe-keys (xs) + "Remove the entries in XS where the keys are `equal'.") + +(defun al-dedupe-entries (xs) + "Remove the entries in XS where the key-value pair are `equal'." + (delete-dups xs)) + +(defun al-keys (xs) + "Return a list of the keys in XS." + (mapcar 'car xs)) + +(defun al-values (xs) + "Return a list of the values in XS." + (mapcar 'cdr xs)) + +(defun al-has-key? (k xs) + "Return t if XS has a key `equal' to K." + (maybe-some? (assoc k xs))) + +(defun al-has-value? (v xs) + "Return t if XS has a value of V." + (maybe-some? (rassoc v xs))) + +(defun al-count (xs) + "Return the number of entries in XS." + (length xs)) + +;; TODO: Should I support `al-find-key' and `al-find-value' variants? +(defun al-find (p xs) + "Find an element in XS. + +Apply a predicate fn, P, to each key and value in XS and return the key of the +first element that returns t." + (let ((result (list-find (lambda (x) (funcall p (car x) (cdr x))) xs))) + (if result + (car result) + nil))) + +(defun al-map-keys (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(funcall f (car x)) . ,(cdr x))) + xs)) + +(defun al-map-values (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(car x) . ,(funcall f (cdr x)))) + xs)) + +(defun al-reduce (acc f xs) + "Return a new alist by calling F on k v and ACC from XS. +F should return a tuple. See tuple.el for more information." + (->> (al-keys xs) + (list-reduce acc + (lambda (k acc) + (funcall f k (al-get k xs) acc))))) + +(defun al-merge (a b) + "Return a new alist with a merge of alists, A and B. +In this case, the last writer wins, which is B." + (al-reduce a #'al-set b)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when al-enable-tests? + (prelude-assert + (equal '((2 . one) + (3 . two)) + (al-map-keys #'1+ + '((1 . one) + (2 . two))))) + (prelude-assert + (equal '((one . 2) + (two . 3)) + (al-map-values #'1+ + '((one . 1) + (two . 2)))))) + + +;; TODO: Support test cases for the entire API. + +(provide 'al) +;;; al.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bag.el b/users/wpcarro/emacs/.emacs.d/wpc/bag.el new file mode 100644 index 000000000000..38a09d94f900 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bag.el @@ -0,0 +1,71 @@ +;;; bag.el --- Working with bags (aka multi-sets) -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; What is a bag? A bag should be thought of as a frequency table. It's a way +;; to convert a list of something into a set that allows duplicates. Isn't +;; allowing duplicates the whole thing with Sets? Kind of. But the interface +;; of Sets is something that bags resemble, so multi-set isn't as bag of a name +;; as it may first seem. +;; +;; If you've used Python's collections.Counter, the concept of a bag should be +;; familiar already. +;; +;; Interface: +;; - add :: x -> Bag(x) -> Bag(x) +;; - remove :: x -> Bag(x) -> Bag(x) +;; - union :: Bag(x) -> Bag(x) -> Bag(x) +;; - difference :: Bag(x) -> Bag(x) -> Bag(x) + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bag xs) + +(defun bag-update (f xs) + "Call F on alist in XS." + (let ((ys (bag-xs xs))) + (setf (bag-xs xs) (funcall f ys)))) + +(defun bag-new () + "Create an empty bag." + (make-bag :xs (al-new))) + +(defun bag-contains? (x xs) + "Return t if XS has X." + (al-has-key? x (bag-xs xs))) + +;; TODO: Tabling this for now since working with structs seems to be +;; disappointingly difficult. Where is `struct-update'? +;; (defun bag-add (x xs) +;; "Add X to XS.") + +;; TODO: What do we name delete vs. remove? +;; (defun bag-remove (x xs) +;; "Remove X from XS. +;; This is a no-op is X doesn't exist in XS.") + +(defun bag-from-list (xs) + "Map a list of `XS' into a bag." + (->> xs + (list-reduce + (bag-new) + (lambda (x acc) + (bag-add x 1 #'number-inc acc))))) + +(provide 'bag) +;;; bag.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el new file mode 100644 index 000000000000..76fc6fe4d75b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el @@ -0,0 +1,100 @@ +;;; bookmark.el --- Saved files and directories on my filesystem -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; After enjoying and relying on Emacs's builtin `jump-to-register' command, I'd +;; like to recreate this functionality with a few extensions. +;; +;; Everything herein will mimmick my previous KBDs for `jump-to-register', which +;; were <leader>-j-<register-kbd>. If the `bookmark-path' is a file, Emacs will +;; open a buffer with that file. If the `bookmark-path' is a directory, Emacs +;; will open an ivy window searching that directory. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'buffer) +(require 'list) +(require 'string) +(require 'set) +(require 'constants) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +;; TODO: Consider hosting this function somewhere other than here, since it +;; feels useful above of the context of bookmarks. +;; TODO: Assess whether it'd be better to use the existing function: +;; `counsel-projectile-switch-project-action'. See the noise I made on GH for +;; more context: https://github.com/ericdanan/counsel-projectile/issues/137 + +(defun bookmark-handle-directory-dwim (path) + "Open PATH as either a project directory or a regular directory. +If PATH is `projectile-project-p', open with `counsel-projectile-find-file'. +Otherwise, open with `counsel-find-file'." + (if (projectile-project-p path) + (with-temp-buffer + (cd (projectile-project-p path)) + (call-interactively #'counsel-projectile-find-file)) + (let ((ivy-extra-directories nil)) + (counsel-find-file path)))) + +(defconst bookmark-handle-directory #'bookmark-handle-directory-dwim + "Function to call when a bookmark points to a directory.") + +(defconst bookmark-handle-file #'counsel-find-file-action + "Function to call when a bookmark points to a file.") + +(defconst bookmark-whitelist + (list + (make-bookmark :label "briefcase" + :path constants-briefcase + :kbd "b") + (make-bookmark :label "current project" + :path constants-current-project + :kbd "p")) + "List of registered bookmarks.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bookmark-open (b) + "Open bookmark, B, in a new buffer or an ivy minibuffer." + (let ((path (bookmark-path b))) + (cond + ((f-directory? path) + (funcall bookmark-handle-directory path)) + ((f-file? path) + (funcall bookmark-handle-file path))))) + + +(defun bookmark-install-kbds () + "Install the keybindings defined herein." + (->> bookmark-whitelist + (list-map + (lambda (b) + (general-define-key + :prefix "<SPC>" + :states '(normal) + (format "J%s" (bookmark-kbd b)) + (lambda () (interactive) (find-file (bookmark-path b))) + (format "j%s" (bookmark-kbd b)) + ;; TODO: Consider `cl-labels' so `which-key' minibuffer is more + ;; helpful. + (lambda () (interactive) (bookmark-open b))))))) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/buffer.el b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000000..3c78601b79f3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el @@ -0,0 +1,206 @@ +;;; buffer.el --- Working with buffers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Utilities for CRUDing buffers in Emacs. +;; +;; Many of these functions may seem unnecessary especially when you consider +;; there implementations. In general I believe that Elisp suffers from a +;; library disorganization problem. Providing simple wrapper functions that +;; rename functions or reorder parameters is worth the effort in my opinion if +;; it improves discoverability (via intuition) and improve composability. +;; +;; I support three ways for switching between what I'm calling "source code +;; buffers": +;; 1. Toggling previous: <SPC><SPC> +;; 2. Using `ivy-read': <SPC>b +;; TODO: These obscure evil KBDs. Maybe a hydra definition would be best? +;; 3. Cycling (forwards/backwards): C-f, C-b + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'maybe) +(require 'set) +(require 'cycle) +(require 'struct) +(require 'ts) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst buffer-enable-tests? t + "When t, run the test suite.") + +(defconst buffer-install-kbds? t + "When t, install the keybindings defined herein.") + +(defconst buffer-source-code-blacklist + (set-new 'dired-mode + 'erc-mode + 'vterm-mode + 'magit-status-mode + 'magit-process-mode + 'magit-log-mode + 'magit-diff-mode + 'org-mode + 'fundamental-mode) + "A blacklist of major-modes to ignore for listing source code buffers.") + +(defconst buffer-source-code-timeout 2 + "Number of seconds to wait before invalidating the cycle.") + +(cl-defstruct source-code-cycle cycle last-called) + +(defun buffer-emacs-generated? (name) + "Return t if buffer, NAME, is an Emacs-generated buffer. +Some buffers are Emacs-generated but are surrounded by whitespace." + (let ((trimmed (s-trim name))) + (and (s-starts-with? "*" trimmed)))) + +(defun buffer-find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-name)) + +(defun buffer-major-mode (name) + "Return the active `major-mode' in buffer, NAME." + (with-current-buffer (buffer-find name) + major-mode)) + +(defun buffer-source-code-buffers () + "Return a list of source code buffers. +This will ignore Emacs-generated buffers, like *Messages*. It will also ignore + any buffer whose major mode is defined in `buffer-source-code-blacklist'." + (->> (buffer-list) + (list-map #'buffer-name) + (list-reject #'buffer-emacs-generated?) + (list-reject (lambda (name) + (set-contains? (buffer-major-mode name) + buffer-source-code-blacklist))))) + +(defvar buffer-source-code-cycle-state + (make-source-code-cycle + :cycle (cycle-from-list (buffer-source-code-buffers)) + :last-called (ts-now)) + "State used to manage cycling between source code buffers.") + +(defun buffer-exists? (name) + "Return t if buffer, NAME, exists." + (maybe-some? (buffer-find name))) + +(defun buffer-new (name) + "Return a newly created buffer NAME." + (generate-new-buffer name)) + +(defun buffer-find-or-create (name) + "Find or create buffer, NAME. +Return a reference to that buffer." + (let ((x (buffer-find name))) + (if (maybe-some? x) + x + (buffer-new name)))) + +;; TODO: Should this consume: `display-buffer' or `switch-to-buffer'? +(defun buffer-show (buffer-or-name) + "Display the BUFFER-OR-NAME, which is either a buffer reference or its name." + (display-buffer buffer-or-name)) + +;; TODO: Move this and `buffer-cycle-prev' into a separate module that +;; encapsulates all of this behavior. + +(defun buffer-cycle (cycle-fn) + "Using CYCLE-FN, move through `buffer-source-code-buffers'." + (let ((last-called (source-code-cycle-last-called + buffer-source-code-cycle-state)) + (cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (if (> (ts-diff (ts-now) last-called) + buffer-source-code-timeout) + (progn + (struct-set! source-code-cycle + cycle + (cycle-from-list (buffer-source-code-buffers)) + buffer-source-code-cycle-state) + (let ((cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle))) + (struct-set! source-code-cycle + last-called + (ts-now) + buffer-source-code-cycle-state)) + (progn + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle)))))) + +(defun buffer-cycle-next () + "Cycle forward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-next)) + +(defun buffer-cycle-prev () + "Cycle backward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-prev)) + +(defun buffer-ivy-source-code () + "Use `ivy-read' to choose among all open source code buffers." + (interactive) + (ivy-read "Source code buffer: " + (-drop 1 (buffer-source-code-buffers)) + :sort nil + :action #'switch-to-buffer)) + +(defun buffer-show-previous () + "Call `switch-to-buffer' on the previously visited buffer. +This function ignores Emacs-generated buffers, i.e. the ones that look like + this: *Buffer*. It also ignores buffers that are `dired-mode' or `erc-mode'. + This blacklist can easily be changed." + (interactive) + (let* ((xs (buffer-source-code-buffers)) + (candidate (list-get 1 xs))) + (prelude-assert (maybe-some? candidate)) + (switch-to-buffer candidate))) + +(when buffer-install-kbds? + (general-define-key + :states '(normal) + "C-f" #'buffer-cycle-next + "C-b" #'buffer-cycle-prev) + (general-define-key + :prefix "<SPC>" + :states '(normal) + "b" #'buffer-ivy-source-code + "<SPC>" #'buffer-show-previous + "k" #'kill-buffer)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when buffer-enable-tests? + (prelude-assert + (list-all? #'buffer-emacs-generated? + '("*scratch*" + "*Messages*" + "*shell*" + "*Shell Command Output*" + "*Occur*" + "*Warnings*" + "*Help*" + "*Completions*" + "*Apropos*" + "*info*")))) + +(provide 'buffer) +;;; buffer.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bytes.el b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el new file mode 100644 index 000000000000..48d3932f1cf8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el @@ -0,0 +1,113 @@ +;;; bytes.el --- Working with byte values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Functions to help with human-readable representations of byte values. +;; +;; Usage: +;; See the test cases for example usage. Or better yet, I should use a type of +;; structured documentation that would allow me to expose a view into the test +;; suite here. Is this currently possible in Elisp? +;; +;; API: +;; - serialize :: Integer -> String +;; +;; Wish list: +;; - Rounding: e.g. (bytes (* 1024 1.7)) => "2KB" + +;;; Code: + +;; TODO: Support -ibabyte variants like Gibibyte (GiB). + +;; Ranges: +;; B: [ 0, 1e3) +;; KB: [ 1e3, 1e6) +;; MB: [ 1e6, 1e6) +;; GB: [ 1e9, 1e12) +;; TB: [1e12, 1e15) +;; PB: [1e15, 1e18) +;; +;; Note: I'm currently not support exabytes because that causes the integer to +;; overflow. I imagine a larger integer type may exist, but for now, I'll +;; treat this as a YAGNI. + +(require 'prelude) +(require 'tuple) +(require 'math) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst bytes-kb (math-exp 2 10) + "Number of bytes in a kilobyte.") + +(defconst bytes-mb (math-exp 2 20) + "Number of bytes in a megabytes.") + +(defconst bytes-gb (math-exp 2 30) + "Number of bytes in a gigabyte.") + +(defconst bytes-tb (math-exp 2 40) + "Number of bytes in a terabyte.") + +(defconst bytes-pb (math-exp 2 50) + "Number of bytes in a petabyte.") + +(defconst bytes-eb (math-exp 2 60) + "Number of bytes in an exabyte.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bytes-classify (x) + "Return unit that closest fits byte count, X." + (prelude-assert (number-whole? x)) + (cond + ((and (>= x 0) (< x bytes-kb)) 'byte) + ((and (>= x bytes-kb) (< x bytes-mb)) 'kilobyte) + ((and (>= x bytes-mb) (< x bytes-gb)) 'megabyte) + ((and (>= x bytes-gb) (< x bytes-tb)) 'gigabyte) + ((and (>= x bytes-tb) (< x bytes-pb)) 'terabyte) + ((and (>= x bytes-pb) (< x bytes-eb)) 'petabyte))) + +(defun bytes-to-string (x) + "Convert integer X into a human-readable string." + (let ((base-and-unit + (pcase (bytes-classify x) + ('byte (tuple/from 1 "B")) + ('kilobyte (tuple/from bytes-kb "KB")) + ('megabyte (tuple/from bytes-mb "MB")) + ('gigabyte (tuple/from bytes-gb "GB")) + ('terabyte (tuple/from bytes-tb "TB")) + ('petabyte (tuple/from bytes-pb "PB"))))) + (string-format "%d%s" + (round x (tuple/first base-and-unit)) + (tuple/second base-and-unit)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude-assert + (equal "1000B" (bytes-to-string 1000))) + (prelude-assert + (equal "2KB" (bytes-to-string (* 2 bytes-kb)))) + (prelude-assert + (equal "17MB" (bytes-to-string (* 17 bytes-mb)))) + (prelude-assert + (equal "419GB" (bytes-to-string (* 419 bytes-gb)))) + (prelude-assert + (equal "999TB" (bytes-to-string (* 999 bytes-tb)))) + (prelude-assert + (equal "2PB" (bytes-to-string (* 2 bytes-pb))))) + +(provide 'bytes) +;;; bytes.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/cache.el b/users/wpcarro/emacs/.emacs.d/wpc/cache.el new file mode 100644 index 000000000000..be2049091c6d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cache.el @@ -0,0 +1,89 @@ +;;; cache.el --- Caching things -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; An immutable cache data structure. +;; +;; This is like a sideways stack, that you can pull values out from and re-push +;; to the top. It'd be like a stack supporting push, pop, pull. +;; +;; This isn't a key-value data-structure like you might expect from a +;; traditional cache. The name is subject to change, but the underlying idea of +;; a cache remains the same. +;; +;; Think about prescient.el, which uses essentially an LRU cache integrated into +;; counsel to help create a "clairovoyant", self-organizing list. +;; +;; Use-cases: +;; - Keeps an cache of workspaces sorted as MRU with an LRU eviction strategy. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct cache xs) + +;; TODO: Prefer another KBD for yasnippet form completion than company-mode's +;; current KBD. + +(defun cache-from-list (xs) + "Turn list, XS, into a cache." + (make-cache :xs xs)) + +(defun cache-contains? (x xs) + "Return t if X in XS." + (->> xs + cache-xs + (list-contains? x))) + +(defun cache-touch (x xs) + "Ensure value X in cache, XS, is front of the list. +If X isn't in XS (using `equal'), insert it at the front." + (struct-update + cache + xs + (>-> (list-reject (lambda (y) (equal x y))) + (list-cons x)) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (let ((cache (cache-from-list '("chicken" "nugget")))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; contains?/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude-refute + (cache-contains? "turkey" cache)) + (prelude-assert + (cache-contains? "chicken" cache)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; touch/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude-assert + (equal + (cache-touch "nugget" cache) + (cache-from-list '("nugget" "chicken")))) + (prelude-assert + (equal + (cache-touch "spicy" cache) + (cache-from-list '("spicy" "chicken" "nugget")))))) + +(provide 'cache) +;;; cache.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000000..47cc459061e3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el @@ -0,0 +1,44 @@ +;;; clipboard.el --- Working with X11's pasteboard -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Simple functions for copying and pasting. +;; +;; Integrate with bburns/clipmon so that System Clipboard can integrate with +;; Emacs's kill-ring. +;; +;; Wish list: +;; - Create an Emacs integration with github.com/cdown/clipmenud. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun clipboard-copy (x &key (message "[clipboard.el] Copied!")) + "Copy string, X, to X11's clipboard and `message' MESSAGE." + (kill-new x) + (message message)) + +(cl-defun clipboard-paste (&key (message "[clipboard.el] Pasted!")) + "Paste contents of X11 clipboard and `message' MESSAGE." + (yank) + (message message)) + +(defun clipboard-contents () + "Return the contents of the clipboard as a string." + (substring-no-properties (current-kill 0))) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el new file mode 100644 index 000000000000..a02dc67c56a9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el @@ -0,0 +1,86 @@ +;;; colorscheme.el --- Syntax highlight and friends -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; +;; TODO: Clarify this. +;; Since I have my own definition of "theme", which couples wallpaper, font, +;; with Emacs's traditional notion of the word "theme", I'm choosing to use +;; "colorscheme" to refer to *just* the notion of syntax highlight etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cycle) +(require '>) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme-whitelist + (cycle-from-list + (->> (custom-available-themes) + (list-map #'symbol-name) + (list-filter (>-> (s-starts-with? "doom-"))) + (list-map #'intern))) + "The whitelist of colorschemes through which to cycle.") + +(defun colorscheme-current () + "Return the currently enabled colorscheme." + (cycle-current colorscheme-whitelist)) + +(defun colorscheme-disable-all () + "Disable all currently enabled colorschemes." + (interactive) + (->> custom-enabled-themes + (list-map #'disable-theme))) + +(defun colorscheme-set (theme) + "Call `load-theme' with `THEME', ensuring that the line numbers are bright. +There is no hook that I'm aware of to handle this more elegantly." + (load-theme theme t) + (prelude-set-line-number-color "#da5468")) + +(defun colorscheme-whitelist-set (colorscheme) + "Focus the COLORSCHEME in the `colorscheme-whitelist' cycle." + (cycle-focus (lambda (x) (equal x colorscheme)) colorscheme-whitelist) + (colorscheme-set (colorscheme-current))) + +(defun colorscheme-ivy-select () + "Load a colorscheme using ivy." + (interactive) + (let ((theme (ivy-read "Theme: " (cycle-to-list colorscheme-whitelist)))) + (colorscheme-disable-all) + (colorscheme-set (intern theme)))) + +(cl-defun colorscheme-cycle (&key forward?) + "Cycle next if `FORWARD?' is non-nil. +Cycle prev otherwise." + (disable-theme (cycle-current colorscheme-whitelist)) + (let ((theme (if forward? + (cycle-next colorscheme-whitelist) + (cycle-prev colorscheme-whitelist)))) + (colorscheme-set theme) + (message (s-concat "Active theme: " (symbol-to-string theme))))) + +(defun colorscheme-next () + "Disable the currently active theme and load the next theme." + (interactive) + (colorscheme-cycle :forward? t)) + +(defun colorscheme-prev () + "Disable the currently active theme and load the previous theme." + (interactive) + (colorscheme-cycle :forward? nil)) + +(provide 'colorscheme) +;;; colorscheme.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/constants.el b/users/wpcarro/emacs/.emacs.d/wpc/constants.el new file mode 100644 index 000000000000..ae21a089cc05 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/constants.el @@ -0,0 +1,55 @@ +;;; constants.el --- Constants for organizing my Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains constants that are shared across my configuration. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'maybe) + +(prelude-assert (f-exists? (getenv "BRIEFCASE"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst constants-ci? + (maybe-some? (getenv "CI")) + "Encoded as t when Emacs is running in CI.") + +(defconst constants-briefcase + (getenv "BRIEFCASE") + "Path to my monorepo, which various parts of my configuration rely on.") + +;; TODO: Consider merging `ui.el' and `misc.el' because those are the only +;; current consumers of these constants, and I'm unsure if the indirection that +;; globally defined constants introduces is worth it. + +(defconst constants-current-project + constants-briefcase + "Variable holding the directory for my currently active project.") + +(defconst constants-mouse-kbds + '([mouse-1] [down-mouse-1] [drag-mouse-1] [double-mouse-1] [triple-mouse-1] + [mouse-2] [down-mouse-2] [drag-mouse-2] [double-mouse-2] [triple-mouse-2] + [mouse-3] [down-mouse-3] [drag-mouse-3] [double-mouse-3] [triple-mouse-3] + [mouse-4] [down-mouse-4] [drag-mouse-4] [double-mouse-4] [triple-mouse-4] + [mouse-5] [down-mouse-5] [drag-mouse-5] [double-mouse-5] [triple-mouse-5]) + "All of the mouse-related keybindings that Emacs recognizes.") + +(defconst constants-fill-column 80 + "Variable used to set the defaults for wrapping, highlighting, etc.") + +(provide 'constants) +;;; constants.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/cycle.el b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el new file mode 100644 index 000000000000..5ea015930017 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el @@ -0,0 +1,224 @@ +;;; cycle.el --- Simple module for working with cycles -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Something like this may already exist, but I'm having trouble finding it, and +;; I think writing my own is a nice exercise for learning more Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'math) +(require 'maybe) +(require 'struct) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish list +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Provide immutable variant. +;; - TODO: Replace mutable consumption with immutable variant. +;; - TODO: Replace indexing with (math-mod current cycle). + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `current-index' tracks the current index +;; `xs' is the original list +(cl-defstruct cycle current-index previous-index xs) + +(defconst cycle-enable-tests? t + "When t, run the tests defined herein.") + +(defun cycle-from-list (xs) + "Create a cycle from a list of `XS'." + (if (= 0 (length xs)) + (make-cycle :current-index nil + :previous-index nil + :xs xs) + (make-cycle :current-index 0 + :previous-index nil + :xs xs))) + +(defun cycle-new (&rest xs) + "Create a cycle with XS as the values." + (cycle-from-list xs)) + +(defun cycle-to-list (xs) + "Return the list representation of a cycle, XS." + (cycle-xs xs)) + +(defun cycle--next-index<- (lo hi x) + "Return the next index in a cycle when moving downwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (< (- x 1) lo) + (- hi 1) + (- x 1))) + +(defun cycle--next-index-> (lo hi x) + "Return the next index in a cycle when moving upwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (>= (+ 1 x) hi) + lo + (+ 1 x))) + +(defun cycle-previous-focus (cycle) + "Return the previously focused entry in CYCLE." + (let ((i (cycle-previous-index cycle))) + (if (maybe-some? i) + (nth i (cycle-xs cycle)) + nil))) + +;; TODO: Consider adding "!" to the function name herein since many of them +;; mutate the collection, and the APIs are beginning to confuse me. +(defun cycle-focus-previous! (xs) + "Jump to the item in XS that was most recently focused; return the cycle. +This will error when previous-index is nil. This function mutates the +underlying struct." + (let ((i (cycle-previous-index xs))) + (if (maybe-some? i) + (progn + (cycle-jump i xs) + (cycle-current xs)) + (error "Cannot focus the previous element since cycle-previous-index is nil")))) + +(defun cycle-next (xs) + "Return the next value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index-> 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-prev (xs) + "Return the previous value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index<- 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-current (cycle) + "Return the current value in `CYCLE'." + (nth (cycle-current-index cycle) (cycle-xs cycle))) + +(defun cycle-count (cycle) + "Return the length of `xs' in `CYCLE'." + (length (cycle-xs cycle))) + +(defun cycle-jump (i xs) + "Jump to the I index of XS." + (let ((current-index (cycle-current-index xs)) + (next-index (math-mod i (cycle-count xs)))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs)) + xs) + +(defun cycle-focus (p cycle) + "Focus the element in CYCLE for which predicate, P, is t." + (let ((i (->> cycle + cycle-xs + (-find-index p)))) + (if i + (cycle-jump i cycle) + (error "No element in cycle matches predicate")))) + +(defun cycle-focus-item (x xs) + "Focus item, X, in cycle XS. +ITEM is the first item in XS that t for `equal'." + (cycle-focus (lambda (y) (equal x y)) xs)) + +(defun cycle-contains? (x xs) + "Return t if cycle, XS, has member X." + (->> xs + cycle-xs + (list-contains? x))) + +(defun cycle-empty? (xs) + "Return t if cycle XS has no elements." + (= 0 (length (cycle-xs xs)))) + +(defun cycle-focused? (xs) + "Return t if cycle XS has a non-nil value for current-index." + (maybe-some? (cycle-current-index xs))) + +(defun cycle-append (x xs) + "Add X to the left of the focused element in XS. +If there is no currently focused item, add X to the beginning of XS." + (if (cycle-empty? xs) + (progn + (struct-set! cycle xs (list x) xs) + (struct-set! cycle current-index 0 xs) + (struct-set! cycle previous-index nil xs)) + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs))) + (if curr-i + (progn + (struct-set! cycle xs (-insert-at curr-i x (cycle-xs xs)) xs) + (when (>= prev-i curr-i) (struct-set! cycle previous-index (1+ prev-i) xs)) + (when curr-i (struct-set! cycle current-index (1+ curr-i) xs))) + (progn + (struct-set! cycle xs (cons x (cycle-xs xs)) xs) + (when prev-i (struct-set! cycle previous-index (1+ prev-i) xs)))) + xs))) + +(defun cycle-remove (x xs) + "Attempt to remove X from XS. + +X is found using `equal'. + +If X is the currently focused value, after it's deleted, current-index will be + nil. If X is the previously value, after it's deleted, previous-index will be + nil." + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs)) + (rm-i (-elem-index x (cycle-xs xs)))) + (struct-set! cycle xs (-remove-at rm-i (cycle-xs xs)) xs) + (when prev-i + (when (> prev-i rm-i) (struct-set! cycle previous-index (1- prev-i) xs)) + (when (= prev-i rm-i) (struct-set! cycle previous-index nil xs))) + (when curr-i + (when (> curr-i rm-i) (struct-set! cycle current-index (1- curr-i) xs)) + (when (= curr-i rm-i) (struct-set! cycle current-index nil xs))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when cycle-enable-tests? + (let ((xs (cycle-new 1 2 3))) + (prelude-assert (maybe-nil? (cycle-previous-focus xs))) + (prelude-assert (= 1 (cycle-current xs))) + (prelude-assert (= 2 (cycle-next xs))) + (prelude-assert (= 1 (cycle-previous-focus xs))) + (prelude-assert (= 1 (->> xs (cycle-jump 0) cycle-current))) + (prelude-assert (= 2 (->> xs (cycle-jump 1) cycle-current))) + (prelude-assert (= 3 (->> xs (cycle-jump 2) cycle-current))) + (prelude-assert (= 2 (cycle-previous-focus xs))) + (prelude-assert (= 2 (cycle-focus-previous! xs))) + (prelude-assert (equal '(1 4 2 3) (cycle-xs (cycle-append 4 xs)))) + (prelude-assert (equal '(1 2 3) (cycle-xs (cycle-remove 4 xs)))) + (progn + (cycle-focus-item 3 xs) + (cycle-focus-item 2 xs) + (cycle-remove 1 xs) + (prelude-assert (= 2 (cycle-current xs))) + (prelude-assert (= 3 (cycle-previous-focus xs)))))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/device.el b/users/wpcarro/emacs/.emacs.d/wpc/device.el new file mode 100644 index 000000000000..0e7992fd79e7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/device.el @@ -0,0 +1,50 @@ +;;; device.el --- Physical device information -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Functions for querying device information. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'al) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst device-hostname->device + '(("zeno.lon.corp.google.com" . work-desktop) + ("seneca" . work-laptop)) + "Mapping hostname to a device symbol.") + +;; TODO: Should I generate these predicates? + +(defun device-classify () + "Return the device symbol for the current host or nil if not supported." + (al-get system-name device-hostname->device)) + +(defun device-work-laptop? () + "Return t if current device is work laptop." + (equal 'work-laptop + (device-classify))) + +(defun device-work-desktop? () + "Return t if current device is work desktop." + (equal 'work-desktop + (device-classify))) + +(defun device-corporate? () + "Return t if the current device is owned by my company." + (or (device-work-laptop?) (device-work-desktop?))) + +(provide 'device) +;;; device.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/display.el b/users/wpcarro/emacs/.emacs.d/wpc/display.el new file mode 100644 index 000000000000..24c00e3f73ea --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/display.el @@ -0,0 +1,138 @@ +;;; display.el --- Working with single or multiple displays -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mostly wrappers around xrandr. +;; +;; Troubleshooting: +;; The following commands help me when I (infrequently) interact with xrandr. +;; - xrandr --listmonitors +;; - xrandr --query + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defmacro display-register (name &key + output + primary + coords + size + rate + dpi + rotate) + "Macro to define constants and two functions for {en,dis}abling a display. + +NAME - the human-readable identifier for the display +OUTPUT - the xrandr identifier for the display +PRIMARY - if true, send --primary flag to xrandr +COORDS - X and Y offsets +SIZE - the pixel resolution of the display (width height) +RATE - the refresh rate +DPI - the pixel density in dots per square inch +rotate - one of {normal,left,right,inverted} + +See the man-page for xrandr for more details." + `(progn + (defconst ,(intern (format "display-%s" name)) ,output + ,(format "The xrandr identifier for %s" name)) + (defconst ,(intern (format "display-%s-args" name)) + ,(replace-regexp-in-string + "\s+" " " + (s-format "--output ${output} ${primary-flag} --auto \ + --size ${size-x}x${size-y} --rate ${rate} --dpi ${dpi} \ + --rotate ${rotate} ${pos-flag}" + #'aget + `(("output" . ,output) + ("primary-flag" . ,(if primary "--primary" "--noprimary")) + ("pos-flag" . ,(if coords + (format "--pos %dx%d" + (car coords) + (cadr coords)) + "")) + ("size-x" . ,(car size)) + ("size-y" . ,(cadr size)) + ("rate" . ,rate) + ("dpi" . ,dpi) + ("rotate" . ,rotate)))) + ,(format "The arguments we pass to xrandr for display-%s." name)) + (defconst ,(intern (format "display-%s-command" name)) + (format "xrandr %s" ,(intern (format "display-%s-args" name))) + ,(format "The command we run to configure %s" name)) + (defun ,(intern (format "display-enable-%s" name)) () + ,(format "Attempt to enable my %s monitor" name) + (interactive) + (prelude-start-process + :name ,(format "display-enable-%s" name) + :command ,(intern (format "display-%s-command" name)))) + (defun ,(intern (format "display-disable-%s" name)) () + ,(format "Attempt to disable my %s monitor." name) + (interactive) + (prelude-start-process + :name ,(format "display-disable-%s" name) + :command ,(format + "xrandr --output %s --off" + output))))) + +(defmacro display-arrangement (name &key displays) + "Create a function, display-arrange-<NAME>, to enable all your DISPLAYS." + `(defun ,(intern (format "display-arrange-%s" name)) () + (interactive) + (prelude-start-process + :name ,(format "display-configure-%s" name) + :command ,(format "xrandr %s" + (->> displays + (-map (lambda (x) + (eval (intern (format "display-%s-args" x))))) + (s-join " ")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(display-register laptop + :output "eDP1" + :primary nil + :coords (2560 1440) + :size (1920 1080) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-horizontal + :output "DP2" + :primary t + :coords (0 0) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-vertical + :output "HDMI1" + :primary nil + :coords (-1440 -560) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate left) + +(display-arrangement primary + :displays (laptop 4k-horizontal 4k-vertical)) + +(provide 'display) +;;; display.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/dotted.el b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el new file mode 100644 index 000000000000..f400affd6ec6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el @@ -0,0 +1,58 @@ +;;; dotted.el --- Working with dotted pairs in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Part of my primitives library extensions in Elisp. Contrast my primitives +;; with the wrapper extensions that I provide, which expose immutable variants +;; of data structures like an list, alist, tuple, as well as quasi-typeclasses +;; like sequence, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun dotted-new (&optional a b) + "Create a new dotted pair of A and B." + (cons a b)) + +(defun dotted-instance? (x) + "Return t if X is a dotted pair." + (let ((b (cdr x))) + (and b (atom b)))) + +(defun dotted-first (x) + "Return the first element of X." + (car x)) + +(defun dotted-second (x) + "Return the second element of X." + (cdr x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude-assert + (equal '(fname . "Bob") (dotted-new 'fname "Bob"))) + (prelude-assert + (dotted-instance? '(one . two))) + (prelude-refute + (dotted-instance? '(1 2 3)))) + +(provide 'dotted) +;;; dotted.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/email.el b/users/wpcarro/emacs/.emacs.d/wpc/email.el new file mode 100644 index 000000000000..ef5d7c9534ed --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/email.el @@ -0,0 +1,77 @@ +;;; email.el --- My email settings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Attempting to configure to `notmuch' for my personal use. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'notmuch) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :key "i") + (:name "direct" + :query "tag:direct and tag:unread and not tag:sent" + :key "d") + (:name "action" :query "tag:action" :key "a") + (:name "review" :query "tag:review" :key "r") + (:name "waiting" :query "tag:waiting" :key "w") + (:name "broadcast" :query "tag:/broadcast\/.+/ and tag:unread" :key "b") + (:name "systems" :query "tag:/systems\/.+/ and tag:unread" :key "s") + (:name "sent" :query "tag:sent" :key "t") + (:name "drafts" :query "tag:draft" :key "D"))) + +;; Sort results from newest-to-oldest. +(setq notmuch-search-oldest-first nil) + +;; Discard noisy email signatures. +(setq notmuch-mua-cite-function #'message-cite-original-without-signature) + +;; By default, this is just '("-inbox") +(setq notmuch-archive-tags '("-inbox" "-unread" "+archive")) + +;; Show saved searches even when they're empty. +(setq notmuch-show-empty-saved-searches t) + +;; Currently the sendmail executable on my system is symlinked to msmtp. +(setq send-mail-function #'sendmail-send-it) + +;; I'm not sure if I need this or not. Copying it from tazjin@'s monorepo. +(setq notmuch-always-prompt-for-sender nil) + +;; Add the "User-Agent" header to my emails and ensure that it includes Emacs +;; and notmuch information. +(setq notmuch-mua-user-agent-function + (lambda () + (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) + +;; I was informed that Gmail does this server-side +(setq notmuch-fcc-dirs nil) + +;; Ensure buffers are closed after sending mail. +(setq message-kill-buffer-on-exit t) + +;; Ensure sender is correctly passed to msmtp. +(setq mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header) + +;; Assert that no two saved searches share share a KBD +(prelude-assert + (list-xs-distinct-by? (lambda (x) (plist-get x :key)) notmuch-saved-searches)) + +(provide 'email) +;;; email.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fonts.el b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000000..ad77b512d78e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el @@ -0,0 +1,172 @@ +;;; fonts.el --- Font preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Control my font preferences with ELisp. + +;;; Code: + +;; TODO: `defcustom' font-size. +;; TODO: `defcustom' fonts. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'cycle) +(require 'device) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Troubleshoot why "8" appears so large on my desktop. + +;; TODO: Consider having a different font size when I'm using my 4K monitor. + +(defconst fonts-size + (pcase (device-classify) + ('work-laptop "10") + ('work-desktop "10")) + "My preferred default font-size, which is device specific.") + +(defconst fonts-size-step 10 + "The amount (%) by which to increase or decrease a font.") + +(defconst fonts-hacker-news-recommendations + '("APL385 Unicode" + "Go Mono" + "Sudo" + "Monoid" + "Input Mono Medium" ;; NOTE: Also "Input Mono Thin" is nice. + ) + "List of fonts optimized for programming I found in a HN article.") + +(defconst fonts-whitelist + (cycle-from-list + (list-concat + fonts-hacker-news-recommendations + '("JetBrainsMono" + "Mononoki Medium" + "Monospace" + "Operator Mono Light" + "Courier" + "Andale Mono" + "Source Code Pro" + "Terminus"))) + "This is a list of my preferred fonts.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: fonts and fonts-whitelist make it difficult to name functions like +;; fonts-set as a generic Emacs function vs choosing a font from the whitelist. + +(cl-defun fonts-cycle (&key forward?) + "Cycle forwards when `FORWARD?' non-nil." + (let ((font (if forward? + (cycle-next fonts-whitelist) + (cycle-prev fonts-whitelist)))) + (message (s-concat "Active font: " font)) + (fonts-set font))) + +(defun fonts-next () + "Quickly cycle through preferred fonts." + (interactive) + (fonts-cycle :forward? t)) + +(defun fonts-prev () + "Quickly cycle through preferred fonts." + (interactive) + (fonts-cycle :forward? nil)) + +(defun fonts-set (font &optional size) + "Change the font to `FONT' with option integer, SIZE, in pixels." + (if (maybe-some? size) + (set-frame-font (string-format "%s %s" font size) nil t) + (set-frame-font font nil t))) + +(defun fonts-whitelist-set (font) + "Focuses the FONT in the `fonts-whitelist' cycle. +The size of the font is determined by `fonts-size'." + (prelude-assert (cycle-contains? font fonts-whitelist)) + (cycle-focus (lambda (x) (equal x font)) fonts-whitelist) + (fonts-set (fonts-current) fonts-size)) + +(defun fonts-ivy-select () + "Select a font from an ivy prompt." + (interactive) + (fonts-whitelist-set + (ivy-read "Font: " (cycle-to-list fonts-whitelist)))) + +(defun fonts-print-current () + "Message the currently enabled font." + (interactive) + (message + (string-format "[fonts] Current font: \"%s\"" + (fonts-current)))) + +(defun fonts-current () + "Return the currently enabled font." + (cycle-current fonts-whitelist)) + +(defun fonts-increase-size () + "Increase font size." + (interactive) + (->> (face-attribute 'default :height) + (+ fonts-size-step) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-decrease-size () + "Decrease font size." + (interactive) + (->> (face-attribute 'default :height) + (+ (- fonts-size-step)) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-reset-size () + "Restore font size to its default value." + (interactive) + (fonts-whitelist-set (fonts-current))) + +(defun fonts-enable-ligatures () + "Call this function to enable ligatures." + (interactive) + (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)") + (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)") ;; + (36 . ".\\(?:>\\)") + (37 . ".\\(?:\\(?:%%\\)\\|%\\)") + (38 . ".\\(?:\\(?:&&\\)\\|&\\)") + (42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)") ;; + (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)") + (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)") + (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)") ;; + (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)") + (48 . ".\\(?:x[a-zA-Z]\\)") + (58 . ".\\(?:::\\|[:=]\\)") + (59 . ".\\(?:;;\\|;\\)") + (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)") + (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)") + (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)") + (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)") + (91 . ".\\(?:]\\)") + (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)") + (94 . ".\\(?:=\\)") + (119 . ".\\(?:ww\\)") + (123 . ".\\(?:-\\)") + (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)") + (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)")))) + (dolist (char-regexp alist) + (set-char-table-range composition-function-table (car char-regexp) + `([,(cdr char-regexp) 0 font-shape-gstring]))))) + +(provide 'fonts) +;;; fonts.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fs.el b/users/wpcarro/emacs/.emacs.d/wpc/fs.el new file mode 100644 index 000000000000..2f83019fd4c9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fs.el @@ -0,0 +1,70 @@ +;;; fs.el --- Make working with the filesystem easier -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; Ergonomic alternatives for working with the filesystem. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'f) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fs-ensure-file (path) + "Ensure that a file and its directories in `PATH' exist. +Will error for inputs with a trailing slash." + (when (s-ends-with? "/" path) + (error (format "Input path has trailing slash: %s" path))) + (->> path + f-dirname + fs-ensure-dir) + (f-touch path)) + +(f-dirname "/tmp/a/b/file.txt") + +(defun fs-ensure-dir (path) + "Ensure that a directory and its ancestor directories in `PATH' exist." + (->> path + f-split + (apply #'f-mkdir))) + +(defun fs-ls (dir &optional full-path?) + "List the files in `DIR' one-level deep. +Should behave similarly in spirit to the Unix command, ls. +If `FULL-PATH?' is set, return the full-path of the files." + (-drop 2 (directory-files dir full-path?))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest fs-test-ensure-file () + (let ((file "/tmp/file/a/b/c/file.txt")) + ;; Ensure this file doesn't exist first to prevent false-positives. + (f-delete file t) + (fs-ensure-file file) + (should (and (f-exists? file) + (f-file? file))))) + +(ert-deftest fs-test-ensure-dir () + (let ((dir "/tmp/dir/a/b/c")) + ;; Ensure the directory doesn't exist. + (f-delete dir t) + (fs-ensure-dir dir) + (should (and (f-exists? dir) + (f-dir? dir))))) + +(provide 'fs) +;;; fs.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/functions.el b/users/wpcarro/emacs/.emacs.d/wpc/functions.el new file mode 100644 index 000000000000..c0b873aaff14 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/functions.el @@ -0,0 +1,47 @@ +;;; functions.el --- Helper functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file hopefully contains friendly APIs that making ELisp development more +;; enjoyable. + +;; TODO: Break these out into separate modules. + +;;; Code: +(defun functions-evil-window-vsplit-right () + "Split the window vertically and focus the right half." + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun functions-evil-window-split-down () + "Split the window horizontal and focus the bottom half." + (interactive) + (evil-window-split) + (windmove-down)) + +(defun functions-create-snippet () + "Create a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun functions-evil-replace-under-point () + "Faster than typing %s//thing/g." + (interactive) + (let ((term (s-replace "/" "\\/" (symbol-to-string (symbol-at-point))))) + (save-excursion + (evil-ex (concat "%s/\\b" term "\\b/"))))) + +(defun functions-buffer-dirname () + "Return the directory name of the current buffer as a string." + (->> buffer-file-name + f-dirname + f-filename)) + +(provide 'functions) +;;; functions.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/graph.el b/users/wpcarro/emacs/.emacs.d/wpc/graph.el new file mode 100644 index 000000000000..9965622bb6d7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/graph.el @@ -0,0 +1,95 @@ +;;; graph.el --- Working with in-memory graphs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; +;; Remember that there are optimal three ways to model a graph: +;; 1. Edge List +;; 2. Vertex Table (a.k.a. Neighbors Table) +;; 3. Adjacency Matrix +;; +;; I may call these "Edges", "Neighbors", "Adjacencies" to avoid verbose naming. +;; For now, I'm avoiding dealing with Adjacency Matrices as I don't have an +;; immediate use-case for them. This is subject to change. +;; +;; There are also hybrid representations of graphs that combine the three +;; aforementioned models. I believe Erlang's digraph module models graphs in +;; Erlang Term Storage (i.e. ETS) this way. +;; TODO: Verify this claim. +;; +;; Graphs can be weighted or unweighted. They can also be directed or +;; undirected. +;; TODO: Create a table explaining all graph variants. +;; +;; TODO: Figure out the relationship of this module and tree.el, which should in +;; principle overlap. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; For now, I'll support storing *either* neighbors or edges in the graph struct +;; as long as both aren't set, since that introduces consistency issues. I may +;; want to handle that use-case in the future, but not now. +(cl-defstruct graph neighbors edges) + +;; TODO: How do you find the starting point for a topo sort? +(defun graph-sort (xs) + "Return a topological sort of XS.") + +(defun graph-from-edges (xs) + "Create a graph struct from the Edge List, XS. +The user must pass in a valid Edge List since asserting on the shape of XS might + be expensive." + (make-graph :edges xs)) + +(defun graph-from-neighbors (xs) + "Create a graph struct from a Neighbors Table, XS. +The user must pass in a valid Neighbors Table since asserting on the shape of + XS might be expensive." + (make-graph :neighbors xs)) + +(defun graph-instance? (xs) + "Return t if XS is a graph struct." + (graph-p xs)) + +;; TODO: Model each of the mapping functions into an isomorphism. +(defun graph-edges->neighbors (xs) + "Map Edge List, XS, into a Neighbors Table." + (prelude-assert (graph-instance? xs))) + +(defun graph-neighbors->edges (xs) + "Map Neighbors Table, XS, into an Edge List." + (prelude-assert (graph-instance? xs))) + +;; Below are three different models of the same unweighted, directed graph. + +(defvar graph-edges + '((a . b) (a . c) (a . e) + (b . c) (b . d) + (c . e) + (d . f) + (e . d) (e . f))) + +(defvar graph-neighbors + ((a b c e) + (b c d) + (c e) + (d f) + (e d g) + (f))) + +(provide 'graph) +;;; graph.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/irc.el b/users/wpcarro/emacs/.emacs.d/wpc/irc.el new file mode 100644 index 000000000000..f221db9eb9e0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/irc.el @@ -0,0 +1,171 @@ +;;; irc.el --- Configuration for IRC chat -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Need to decide which client I will use for IRC. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'erc) +(require 'cycle) +(require 'string) +(require 'prelude) +(require 'al) +(require 'set) +(require 'maybe) +(require 'macros) +(require '>) +(require 'password-store) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom irc-install-kbds? t + "When t, install the keybindings defined herein.") + +(setq erc-rename-buffers t) + +;; Setting `erc-join-buffer' to 'bury prevents erc from stealing focus of the +;; current buffer when it connects to IRC servers. +(setq erc-join-buffer 'bury) + +;; TODO: Find a way to avoid putting "freenode" and "#freenode" as channels +;; here. I'm doing it because when erc first connects, it's `(buffer-name)' is +;; "freenode", so when `irc-next-channel' is called, it 404s on the +;; `cycle-contains?' call in `irc-channel->cycle" unless "freenode" is there. To +;; make matters even uglier, when `erc-join-channel' is called with "freenode" +;; as the value, it connects to the "#freenode" channel, so unless "#freenode" +;; exists in this cycle also, `irc-next-channel' breaks again. +(defconst irc-server->channels + `(("irc.freenode.net" . ,(cycle-new "freenode" "#freenode" "#nixos" "#emacs" "#pass")) + ("irc.corp.google.com" . ,(cycle-new "#drive-prod"))) + "Mapping of IRC servers to a cycle of my preferred channels.") + +;; TODO: Here is another horrible hack that should be revisted. +(setq erc-autojoin-channels-alist + (->> irc-server->channels + (al-map-values #'cycle-to-list) + (al-map-keys (>-> (s-chop-prefix "irc.") + (s-chop-suffix ".net"))))) + +;; TODO: Assert that no two servers have a channel with the same name. We need +;; this because that's the assumption that underpins the `irc-channel->server' +;; function. This will probably be an O(n^2) operation. +(prelude-assert + (set-distinct? (set-from-list + (cycle-to-list + (al-get "irc.freenode.net" + irc-server->channels))) + (set-from-list + (cycle-to-list + (al-get "irc.corp.google.com" + irc-server->channels))))) + +(defun irc-channel->server (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC server from a given CHANNEL." + (let ((result (al-find (lambda (k v) (cycle-contains? channel v)) + server->channels))) + (prelude-assert (maybe-some? result)) + result)) + +(defun irc-channel->cycle (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC's channels cycle from CHANNEL." + (al-get (irc-channel->server server->channels channel) + server->channels)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun irc-message (x) + "Print message X in a structured way." + (message (string-format "[irc.el] %s" x))) + +;; TODO: Integrate Google setup with Freenode setup. + +;; TODO: Support function or KBD for switching to an ERC buffer. + +(defun irc-kill-all-erc-processes () + "Kill all ERC buffers and processes." + (interactive) + (->> (erc-buffer-list) + (-map #'kill-buffer))) + +(defun irc-switch-to-erc-buffer () + "Switch to an ERC buffer." + (interactive) + (let ((buffers (erc-buffer-list))) + (if (list-empty? buffers) + (error "[irc.el] No ERC buffers available") + (switch-to-buffer (list-head (erc-buffer-list)))))) + +(defun irc-connect-to-freenode () + "Connect to Freenode IRC." + (interactive) + (erc-ssl :server "irc.freenode.net" + :port 6697 + :nick "wpcarro" + :password (password-store-get "programming/irc/freenode") + :full-name "William Carroll")) + +;; TODO: Handle failed connections. +(defun irc-connect-to-google () + "Connect to Google's Corp IRC using ERC." + (interactive) + (erc-ssl :server "irc.corp.google.com" + :port 6697 + :nick "wpcarro" + :full-name "William Carroll")) + +;; TODO: Prefer defining these with a less homespun solution. There is a +;; function call `erc-buffer-filter' that would be more appropriate for the +;; implementation of `irc-next-channel' and `irc-prev-channel'. +(defun irc-next-channel () + "Join the next channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc-channel->cycle irc-server->channels (buffer-name)))) + (erc-join-channel + (cycle-next cycle)) + (irc-message + (string-format "Current IRC channel: %s" (cycle-current cycle)))))) + +(defun irc-prev-channel () + "Join the previous channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc-channel->cycle irc-server->channels (buffer-name)))) + (erc-join-channel + (cycle-prev cycle)) + (irc-message + (string-format "Current IRC channel: %s" (cycle-current cycle)))))) + +(add-hook 'erc-mode-hook (macros-disable auto-fill-mode)) +(add-hook 'erc-mode-hook (macros-disable company-mode)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when irc-install-kbds? + (general-define-key + :keymaps 'erc-mode-map + "<C-tab>" #'irc-next-channel + "<C-S-iso-lefttab>" #'irc-prev-channel)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(provide 'irc) +;;; irc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el new file mode 100644 index 000000000000..9f5b7ca8a0f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el @@ -0,0 +1,138 @@ +;;; ivy-clipmenu.el --- Emacs client for clipmenu -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Ivy integration with the clipboard manager, clipmenu. Essentially, clipmenu +;; turns your system clipboard into a list. +;; +;; To use this module, you must first install clipmenu and ensure that the +;; clipmenud daemon is running. Refer to the installation instructions at +;; github.com/cdown/clipmenu for those details. +;; +;; This module intentionally does not define any keybindings since I'd prefer +;; not to presume my users' preferences. Personally, I use EXWM as my window +;; manager, so I call `exwm-input-set-key' and map it to `ivy-clipmenu-copy'. +;; +;; Usually clipmenu integrates with rofi or dmenu. This Emacs module integrates +;; with ivy. Launch this when you want to select a clip. +;; +;; Clipmenu itself supports a variety of environment variables that allow you to +;; customize its behavior. These variables are respected herein. If you'd +;; prefer to customize clipmenu's behavior from within Emacs, refer to the +;; variables defined in this module. +;; +;; For more information: +;; - See `clipmenu --help`. +;; - Visit github.com/cdown/clipmenu. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 's) +(require 'dash) +(require 'ivy) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup ivy-clipmenu nil + "Ivy integration for clipmenu." + :group 'ivy) + +(defcustom ivy-clipmenu-directory + (or (getenv "XDG_RUNTIME_DIR") + (getenv "TMPDIR") + "/tmp") + "Base directory for clipmenu's data." + :type 'string + :group 'ivy-clipmenu) + +(defconst ivy-clipmenu-executable-version 5 + "The major version number for the clipmenu executable.") + +(defconst ivy-clipmenu-cache-directory + (f-join ivy-clipmenu-directory + (format "clipmenu.%s.%s" + ivy-clipmenu-executable-version + (getenv "USER"))) + "Directory where the clips are stored.") + +(defconst ivy-clipmenu-cache-file-pattern + (f-join ivy-clipmenu-cache-directory "line_cache_*") + "Glob pattern matching the locations on disk for clipmenu's labels.") + +(defcustom ivy-clipmenu-history-length + (or (getenv "CM_HISTLENGTH") 25) + "Limit the number of clips in the history. +This value defaults to 25.") + +(defvar ivy-clipmenu-history nil + "History for `ivy-clipmenu-copy'.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun ivy-clipmenu-parse-content (x) + "Parse the label from the entry, X, in clipmenu's line-cache." + (->> (s-split " " x) + (-drop 1) + (s-join " "))) + +(defun ivy-clipmenu-list-clips () + "Return a list of the content of all of the clips." + (->> ivy-clipmenu-cache-file-pattern + f-glob + (-map (lambda (path) + (s-split "\n" (f-read path) t))) + -flatten + (-reject #'s-blank?) + (-sort #'string>) + (-map #'ivy-clipmenu-parse-content) + delete-dups + (-take ivy-clipmenu-history-length))) + +(defun ivy-clipmenu-checksum (content) + "Return the CRC checksum of CONTENT." + (s-trim-right + (with-temp-buffer + (call-process "/bin/bash" nil (current-buffer) nil "-c" + (format "cksum <<<'%s'" content)) + (buffer-string)))) + +(defun ivy-clipmenu-line-to-content (line) + "Map the chosen LINE from the line cache its content from disk." + (->> line + ivy-clipmenu-checksum + (f-join ivy-clipmenu-cache-directory) + f-read)) + +(defun ivy-clipmenu-do-copy (x) + "Copy string, X, to the system clipboard." + (kill-new x) + (message "[ivy-clipmenu.el] Copied!")) + +(defun ivy-clipmenu-copy () + "Use `ivy-read' to select and copy a clip. +It's recommended to bind this function to a globally available keymap." + (interactive) + (let ((ivy-sort-functions-alist nil)) + (ivy-read "Clipmenu: " + (ivy-clipmenu-list-clips) + :history 'ivy-clipmenu-history + :action (lambda (line) + (->> line + ivy-clipmenu-line-to-content + ivy-clipmenu-do-copy))))) + +(provide 'ivy-clipmenu) +;;; ivy-clipmenu.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000000..d13b99353451 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el @@ -0,0 +1,68 @@ +;;; ivy-helpers.el --- More interfaces to ivy -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Hopefully to improve my workflows. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tuple) +(require 'string) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun ivy-helpers-kv (prompt kv f) + "PROMPT users with the keys in KV and return its corresponding value. + +Apply key and value from KV to F." + (ivy-read + prompt + kv + :require-match t + :action (lambda (entry) + (funcall f (car entry) (cdr entry))))) + +(defun ivy-helpers-do-run-external-command (cmd) + "Execute the specified CMD and notify the user when it finishes." + (message "Starting %s..." cmd) + (set-process-sentinel + (start-process-shell-command cmd nil cmd) + (lambda (process event) + (when (string= event "finished\n") + (message "%s process finished." process))))) + +(defun ivy-helpers-list-external-commands () + "Create a list of all external commands available on $PATH." + (cl-loop + for dir in (split-string (getenv "PATH") path-separator) + when (and (file-exists-p dir) (file-accessible-directory-p dir)) + for lsdir = (cl-loop for i in (directory-files dir t) + for bn = (file-name-nondirectory i) + when (and (not (s-contains? "-wrapped" i)) + (not (member bn completions)) + (not (file-directory-p i)) + (file-executable-p i)) + collect bn) + append lsdir into completions + finally return (sort completions 'string-lessp))) + +(defun ivy-helpers-run-external-command () + "Prompts the user with a list of all installed applications to launch." + (interactive) + (let ((external-commands-list (ivy-helpers-list-external-commands))) + (ivy-read "Command:" external-commands-list + :require-match t + :action #'ivy-helpers-do-run-external-command))) + +;;; Code: +(provide 'ivy-helpers) +;;; ivy-helpers.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/kbd.el b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000000..b456f30cba89 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el @@ -0,0 +1,86 @@ +;;; kbd.el --- Elisp keybinding -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; In order to stay organized, I'm attempting to dedicate KBD prefixes to +;; specific functions. I'm hoping I can be more deliberate with my keybinding +;; choices this way. +;; +;; Terminology: +;; For a more thorough overview of the terminology refer to `keybindings.md' +;; file. Here's a brief overview: +;; - workspace: Anything concerning EXWM workspaces. +;; - x11: Anything concerning X11 applications. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'al) +(require 'set) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst kbd-prefixes + '((workspace . "s") + (x11 . "C-s")) + "Mapping of functions to designated keybinding prefixes to stay organized.") + +;; Assert that no keybindings are colliding. +(prelude-assert + (= (al-count kbd-prefixes) + (->> kbd-prefixes + al-values + set-from-list + set-count))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun kbd-raw (f x) + "Return the string keybinding for function F and appendage X. +Values for F include: +- workspace +- x11" + (prelude-assert (al-has-key? f kbd-prefixes)) + (string-format + "%s-%s" + (al-get f kbd-prefixes) + x)) + +(defun kbd-for (f x) + "Return the `kbd' for function F and appendage X. +Values for F include: +- workspace +- x11" + (kbd (kbd-raw f x))) + +;; TODO: Prefer copying human-readable versions to the clipboard. Right now +;; this isn't too useful. +(defun kbd-copy-keycode () + "Copy the pressed key to the system clipboard." + (interactive) + (message "[kbd] Awaiting keypress...") + (let ((key (read-key))) + (clipboard-copy (string-format "%s" key)) + (message (string-format "[kbd] \"%s\" copied!" key)))) + +(defun kbd-print-keycode () + "Prints the pressed keybinding." + (interactive) + (message "[kbd] Awaiting keypress...") + (message (string-format "[kbd] keycode: %s" (read-key)))) + +(provide 'kbd) +;;; kbd.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el new file mode 100644 index 000000000000..bbc7aaa47724 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el @@ -0,0 +1,367 @@ +;;; keybindings.el --- Centralizing my keybindings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Attempting to centralize my keybindings to simplify my configuration. +;; +;; I have some expectations about my keybindings. Here are some of those +;; defined: +;; - In insert mode: +;; - C-a: beginning-of-line +;; - C-e: end-of-line +;; - C-b: backwards-char +;; - C-f: forwards-char + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'functions) +(require 'clipboard) +(require 'screen-brightness) +(require 'pulse-audio) +(require 'scrot) +(require 'ivy-clipmenu) +(require 'general) +(require 'exwm) +(require 'vterm-mgt) +(require 'buffer) +(require 'display) +(require 'device) +(require 'fonts) +(require 'bookmark) +(require 'constants) +(require 'window-manager) + +;; Note: The following lines must be sorted this way. +(setq evil-want-integration t) +(setq evil-want-keybinding nil) +(general-evil-setup) +(require 'evil) +(require 'evil-collection) +(require 'evil-magit) +(require 'evil-commentary) +(require 'evil-surround) +(require 'key-chord) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; General Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Install KBDs like <SPC>jb to search through my monorepo. +(bookmark-install-kbds) + +;; Ensure that evil's command mode behaves with readline bindings. +(general-define-key + :keymaps 'evil-ex-completion-map + "C-a" #'move-beginning-of-line + "C-e" #'move-end-of-line + "C-k" #'kill-line + "C-u" #'evil-delete-whole-line + "C-v" #'evil-paste-after + "C-d" #'delete-char + "C-f" #'forward-char + "M-b" #'backward-word + "M-f" #'forward-word + "M-d" #'kill-word + "M-DEL" #'backward-kill-word + "C-b" #'backward-char) + +(general-mmap + :keymaps 'override + "RET" #'evil-goto-line + "H" #'evil-first-non-blank + "L" #'evil-end-of-line + "_" #'ranger + "-" #'dired-jump + "sl" #'functions-evil-window-vsplit-right + "sh" #'evil-window-vsplit + "sk" #'evil-window-split + "sj" #'functions-evil-window-split-down) + +(general-nmap + :keymaps 'override + "gu" #'browse-url-at-point + "gd" #'xref-find-definitions + ;; Wrapping `xref-find-references' in the `let' binding to prevent xref from + ;; prompting. There are other ways to handle this variable, such as setting + ;; it globally with `setq' or buffer-locally with `setq-local'. For now, I + ;; prefer setting it with `let', which should bind it in the dynamic scope + ;; for the duration of the `xref-find-references' function call. + "gx" (lambda () + (interactive) + (let ((xref-prompt-for-identifier nil)) + (call-interactively #'xref-find-references)))) + +(general-unbind 'motion "M-." "C-p" "<SPC>") +(general-unbind 'normal "s" "M-." "C-p" "C-n") +(general-unbind 'insert "C-v" "C-d" "C-a" "C-e" "C-n" "C-p" "C-k") + +(setq evil-symbol-word-search t) +(evil-mode 1) +(evil-collection-init) +(evil-commentary-mode) +(global-evil-surround-mode 1) + +;; Ensure the Evil search results get centered vertically. +;; When Emacs is run from a terminal, this forces Emacs to redraw itself, which +;; is visually disruptive. +(when window-system + (progn + (defadvice isearch-update + (before advice-for-isearch-update activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-next + (after advice-for-evil-search-next activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-previous + (after advice-for-evil-search-previous activate) + (evil-scroll-line-to-center (line-number-at-pos))))) + +(key-chord-mode 1) +(key-chord-define evil-insert-state-map "jk" 'evil-normal-state) + +;; This may be contraversial, but I never use the prefix key, and I'd prefer to +;; have to bound to the readline function that deletes the entire line. +(general-unbind "C-u") + +(defmacro keybindings-exwm (c fn) + "Bind C to FN using `exwm-input-set-key' with `kbd' applied to C." + `(exwm-input-set-key (kbd ,c) ,fn)) + +(keybindings-exwm "C-M-v" #'ivy-clipmenu-copy) +(keybindings-exwm "<XF86MonBrightnessUp>" #'screen-brightness-increase) +(keybindings-exwm "<XF86MonBrightnessDown>" #'screen-brightness-decrease) +(keybindings-exwm "<XF86AudioMute>" #'pulse-audio-toggle-mute) +(keybindings-exwm "<XF86AudioLowerVolume>" #'pulse-audio-decrease-volume) +(keybindings-exwm "<XF86AudioRaiseVolume>" #'pulse-audio-increase-volume) +(keybindings-exwm "<XF86AudioMicMute>" #'pulse-audio-toggle-microphone) +(keybindings-exwm (kbd-raw 'x11 "s") #'scrot-select) +(keybindings-exwm "<C-M-tab>" #'window-manager-switch-to-exwm-buffer) +(keybindings-exwm (kbd-raw 'workspace "k") #'fonts-increase-size) +(keybindings-exwm (kbd-raw 'workspace "j") #'fonts-decrease-size) +(keybindings-exwm (kbd-raw 'workspace "0") #'fonts-reset-size) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window sizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "C-M-=" #'balance-windows) +(keybindings-exwm "C-M-j" #'shrink-window) +(keybindings-exwm "C-M-k" #'enlarge-window) +(keybindings-exwm "C-M-h" #'shrink-window-horizontally) +(keybindings-exwm "C-M-l" #'enlarge-window-horizontally) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window Management +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-h" #'windmove-left) +(keybindings-exwm "M-j" #'windmove-down) +(keybindings-exwm "M-k" #'windmove-up) +(keybindings-exwm "M-l" #'windmove-right) +(keybindings-exwm "M-\\" #'evil-window-vsplit) +(keybindings-exwm "M--" #'evil-window-split) +(keybindings-exwm "M-q" #'delete-window) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-:" #'eval-expression) +(keybindings-exwm "M-SPC" #'ivy-helpers-run-external-command) +(keybindings-exwm "M-x" #'counsel-M-x) +(keybindings-exwm "<M-tab>" #'window-manager-next-workspace) +(keybindings-exwm "<M-S-iso-lefttab>" #'window-manager-prev-workspace) +(keybindings-exwm "C-M-\\" #'ivy-pass) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm (kbd-raw 'workspace "l") #'window-manager-logout) + +(general-define-key + :keymaps 'override + "M-q" #'delete-window + "<s-return>" #'toggle-frame-fullscreen + "M-h" #'windmove-left + "M-l" #'windmove-right + "M-k" #'windmove-up + "M-j" #'windmove-down + "M-q" #'delete-window) + +;; Support pasting in M-:. +(general-define-key + :keymaps 'read-expression-map + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +(general-define-key + :prefix "<SPC>" + :states '(normal) + "." #'ffap + "gn" #'notmuch + "i" #'counsel-semantic-or-imenu + "I" #'ibuffer + "hk" #'helpful-callable + "hf" #'helpful-function + "hm" #'helpful-macro + "hc" #'helpful-command + "hk" #'helpful-key + "hv" #'helpful-variable + "hp" #'helpful-at-point + "hi" #'info-apropos + "s" #'flyspell-mode + "S" #'sort-lines + "=" #'align + "p" #'flycheck-previous-error + "f" #'project-find-file + "n" #'flycheck-next-error + "N" #'smerge-next + "W" #'balance-windows + "gss" #'magit-status + "gsb" (lambda () + (interactive) + (magit-status constants-briefcase)) + "E" #'refine + "es" #'functions-create-snippet + "l" #'linum-mode + "B" #'magit-blame + "w" #'save-buffer + "r" #'functions-evil-replace-under-point + "R" #'deadgrep) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Vterm +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Show or hide a vterm buffer. I'm intentionally not defining this in +;; vterm-mgt.el because it consumes `buffer-show-previous', and I'd like to +;; avoid bloating vterm-mgt.el with dependencies that others may not want. +(general-define-key (kbd-raw 'x11 "t") + (lambda () + (interactive) + (if (vterm-mgt--instance? (current-buffer)) + (switch-to-buffer (first (buffer-source-code-buffers))) + (call-interactively #'vterm-mgt-find-or-create)))) + +(general-define-key + :keymaps '(vterm-mode-map) + "C-S-n" #'vterm-mgt-instantiate + "C-S-w" #'vterm-mgt-kill + "<C-tab>" #'vterm-mgt-next + "<C-S-iso-lefttab>" #'vterm-mgt-prev + "<s-backspace>" #'vterm-mgt-rename-buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Displays +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when (device-work-laptop?) + (general-define-key + :prefix "<SPC>" + :states '(normal) + "d0" #'display-enable-laptop + "D0" #'display-disable-laptop + "d1" #'display-enable-4k-horizontal + "D1" #'display-disable-4k-horizontal)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; notmuch +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; evil-collection adds many KBDs to notmuch modes. Some of these I find +;; disruptive. +(general-define-key + :states '(normal) + :keymaps '(notmuch-show-mode-map) + "M-j" nil + "M-k" nil + "<C-S-iso-lefttab>" #'notmuch-show-previous-thread-show + "<C-tab>" #'notmuch-show-next-thread-show + "e" #'notmuch-show-archive-message-then-next-or-next-thread) + +;; TODO(wpcarro): Consider moving this to a separate module +(defun keybindings--evil-ex-define-cmd-local (cmd f) + "Define CMD to F locally to a buffer." + (unless (local-variable-p 'evil-ex-commands) + (setq-local evil-ex-commands (copy-alist evil-ex-commands))) + (evil-ex-define-cmd cmd f)) + +;; TODO(wpcarro): Support a macro that can easily define evil-ex commands for a +;; particular mode. +;; Consumption: +;; (evil-ex-for-mode 'notmuch-message-mode +;; "x" #'notmuch-mua-send-and-exit) + +(add-hook 'notmuch-message-mode-hook + (lambda () + (keybindings--evil-ex-define-cmd-local "x" #'notmuch-mua-send-and-exit))) + +;; For now, I'm mimmicking Gmail KBDs that I have memorized and enjoy +(general-define-key + :states '(normal visual) + :keymaps '(notmuch-search-mode-map) + "M" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+muted"))) + "mi" (lambda () + (interactive) + (notmuch-search-tag '("+inbox" "-action" "-review" "-waiting" "-muted"))) + "ma" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+action" "-review" "-waiting"))) + "mr" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "+review" "-waiting"))) + "mw" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "-review" "+waiting"))) + "e" #'notmuch-search-archive-thread) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; magit +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(magit-status-mode-map + magit-log-mode-map + magit-revision-mode-map) + "l" #'evil-forward-char + "L" #'magit-log) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Info-mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; NOTE: I find some of the following, existing KBDs useful: +;; M-x info-apropos +;; u Info-up +;; M-n clone-buffer +(general-define-key + :states '(normal) + :keymaps '(Info-mode-map) + "SPC" nil + "g SPC" #'Info-scroll-up + "RET" #'Info-follow-nearest-node + "<C-tab>" #'Info-next + "<C-S-iso-lefttab>" #'Info-prev + "g l" #'Info-history-back + "g t" #'Info-toc) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ibuffer +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(ibuffer-mode-map) + "M-j" nil + "K" #'ibuffer-do-delete) + +(provide 'keybindings) +;;; keybindings.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000000..d5a3f92f122b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el @@ -0,0 +1,140 @@ +;;; keyboard.el --- Managing keyboard preferences with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Setting key repeat and other values. +;; +;; Be wary of suspiciously round numbers. Especially those divisible by ten! + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'number) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support clamping functions for repeat-{rate,delay} to ensure only valid +;; values are sent to xset. +(defcustom keyboard-repeat-rate 80 + "The number of key repeat signals sent per second.") + +(defcustom keyboard-repeat-delay 170 + "The number of milliseconds before autorepeat starts.") + +(defconst keyboard-repeat-rate-copy keyboard-repeat-rate + "Copy of `keyboard-repeat-rate' to support `keyboard-reset-key-repeat'.") + +(defconst keyboard-repeat-delay-copy keyboard-repeat-delay + "Copy of `keyboard-repeat-delay' to support `keyboard-reset-key-repeat'.") + +(defcustom keyboard-install-preferences? t + "When t, install keyboard preferences.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keyboard-message (x) + "Message X in a structured way." + (message (string-format "[keyboard.el] %s" x))) + +(cl-defun keyboard-set-key-repeat (&key + (rate keyboard-repeat-rate) + (delay keyboard-repeat-delay)) + "Use xset to set the key-repeat RATE and DELAY." + (prelude-start-process + :name "keyboard-set-key-repeat" + :command (string-format "xset r rate %s %s" delay rate))) + +;; NOTE: Settings like this are machine-dependent. For instance I only need to +;; do this on my laptop and other devices where I don't have access to my split +;; keyboard. +;; NOTE: Running keysym Caps_Lock is not idempotent. If this is called more +;; than once, xmodmap will start to error about non-existent Caps_Lock symbol. +;; For more information see here: +;; https://unix.stackexchange.com/questions/108207/how-to-map-caps-lock-as-the-compose-key-using-xmodmap-portably-and-idempotently +(defun keyboard-swap-caps-lock-and-escape () + "Swaps the caps lock and escape keys using xmodmap." + (interactive) + ;; TODO: Ensure these work once the tokenizing in prelude-start-process works + ;; as expected. + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "remove Lock = Caps_Lock") + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "keysym Caps_Lock = Escape")) + +(defun keyboard-inc-repeat-rate () + "Increment `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (number-inc keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (string-format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-dec-repeat-rate () + "Decrement `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (number-dec keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (string-format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-inc-repeat-delay () + "Increment `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (number-inc keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (string-format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-dec-repeat-delay () + "Decrement `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (number-dec keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (string-format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-print-key-repeat () + "Print the currently set values for key repeat." + (interactive) + (keyboard-message + (string-format "Rate: %s. Delay: %s" + keyboard-repeat-rate + keyboard-repeat-delay))) + +(defun keyboard-set-preferences () + "Reset the keyboard preferences to their default values. +NOTE: This function exists because occasionally I unplug and re-plug in a + keyboard and all of the preferences that I set using xset disappear." + (interactive) + (keyboard-swap-caps-lock-and-escape) + (keyboard-set-key-repeat :rate keyboard-repeat-rate + :delay keyboard-repeat-delay) + ;; TODO: Implement this message function as a macro that pulls the current + ;; file name. + (keyboard-message "Keyboard preferences set!")) + +(defun keyboard-reset-key-repeat () + "Set key repeat rate and delay to original values." + (interactive) + (keyboard-set-key-repeat :rate keyboard-repeat-rate-copy + :delay keyboard-repeat-delay-copy) + (keyboard-message "Key repeat preferences reset.")) + +(when keyboard-install-preferences? + (keyboard-set-preferences)) + +(provide 'keyboard) +;;; keyboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el new file mode 100644 index 000000000000..91b2e3125001 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el @@ -0,0 +1,64 @@ +;;; laptop-battery.el --- Display laptop battery information -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Some wrappers to obtain battery information. +;; +;; To troubleshoot battery consumpton look into the CLI `powertop`. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Roadmap +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support functions that work with reporting battery stats. +;; TODO: low-battery-reporting-threshold +;; TODO: charged-battery-reporting-threshold +;; TODO: Format modeline battery information. +;; TODO: Provide better time information in the modeline. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'battery) +(require 'al) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun laptop-battery-available? () + "Return t if battery information is available." + (maybe-some? battery-status-function)) + +(defun laptop-battery-percentage () + "Return the current percentage of the battery." + (->> battery-status-function + funcall + (al-get 112))) + +(defun laptop-battery-print-percentage () + "Return the current percentage of the battery." + (interactive) + (->> (laptop-battery-percentage) + message)) + +(defun laptop-battery-display () + "Display laptop battery percentage in the modeline." + (interactive) + (display-battery-mode 1)) + +(defun laptop-battery-hide () + "Hide laptop battery percentage in the modeline." + (interactive) + (display-battery-mode -1)) + +(provide 'laptop-battery) +;;; laptop-battery.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/list.el b/users/wpcarro/emacs/.emacs.d/wpc/list.el new file mode 100644 index 000000000000..cc91ac1eaf60 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/list.el @@ -0,0 +1,222 @@ +;;; list.el --- Functions for working with lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Since I prefer having the `list-' namespace, I wrote this module to wrap many +;; of the functions that are defined in the the global namespace in ELisp. I +;; sometimes forget the names of these functions, so it's nice for them to be +;; organized like this. +;; +;; Motivation: +;; Here are some examples of function names that I cannot tolerate: +;; - `car': Return the first element (i.e. "head") of a linked list +;; - `cdr': Return the tail of a linked list + +;; As are most APIs for standard libraries that I write, this is heavily +;; influenced by Elixir's standard library. +;; +;; Elixir's List library: +;; - ++/2 +;; - --/2 +;; - hd/1 +;; - tl/1 +;; - in/2 +;; - length/1 +;; +;; Similar libraries: +;; - dash.el: Functional library that mimmicks Clojure. It is consumed herein. +;; - list-utils.el: Utility library that covers things that dash.el may not +;; cover. +;; stream.el: Elisp implementation of streams, "implemented as delayed +;; evaluation of cons cells." + +;; TODO: Consider naming this file linked-list.el. + +;; TODO: Support module-like macro that auto-namespaces functions. + +;; TODO: Consider wrapping most data structures like linked-lists, +;; associative-lists, etc in a `cl-defstruct', so that the dispatching by type +;; can be nominal instead of duck-typing. I'm not sure if this is a good idea +;; or not. If I do this, I should provide isomorphisms to map between idiomatic +;; ways of working with Elisp data structures and my wrapped variants. + +;; TODO: Are function aliases/synonyms even a good idea? Or do they just +;; bloat the API unnecessarily? + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Move `prelude-assert' elsewhere so that I can require it without +;; introducing the circular dependency of list.el -> prelude.el -> list.el. +;;(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst list-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-new () + "Return a new, empty list." + '()) + +(defun list-concat (&rest lists) + "Joins `LISTS' into on list." + (apply #'-concat lists)) + +(defun list-join (joint xs) + "Join a list of strings, XS, with JOINT." + (if (list-empty? xs) + "" + (list-reduce (list-first xs) + (lambda (x acc) + (string-concat acc joint x)) + (list-tail xs)))) + +(defun list-length (xs) + "Return the number of elements in `XS'." + (length xs)) + +(defun list-get (i xs) + "Return the value in `XS' at `I', or nil." + (nth i xs)) + +(defun list-head (xs) + "Return the head of `XS'." + (car xs)) + +;; TODO: Learn how to write proper function aliases. +(defun list-first (xs) + "Alias for `list-head' for `XS'." + (list-head xs)) + +(defun list-tail (xs) + "Return the tail of `XS'." + (cdr xs)) + +(defun list-reverse (xs) + "Reverses `XS'." + (reverse xs)) + +(defun list-cons (x xs) + "Add `X' to the head of `XS'." + (cons x xs)) + +;; map, filter, reduce + +;; TODO: Create function adapters like swap. +;; (defun adapter/swap (f) +;; "Return a new function that wraps `F' and swaps the arguments." +;; (lambda (a b) +;; (funcall f b a))) + +;; TODO: Make this function work. +(defun list-reduce (acc f xs) + "Return over `XS' calling `F' on an element in `XS'and `ACC'." + (-reduce-from (lambda (acc x) (funcall f x acc)) acc xs)) + +(defun list-map (f xs) + "Call `F' on each element of `XS'." + (-map f xs)) + +(defun list-map-indexed (f xs) + "Call `F' on each element of `XS' along with its index." + (-map-indexed (lambda (i x) (funcall f x i)) xs)) + +(defun list-filter (p xs) + "Return a subset of XS where predicate P returned t." + (list-reverse + (list-reduce + '() + (lambda (x acc) + (if (funcall p x) + (list-cons x acc) + acc)) + xs))) + +(defun list-reject (p xs) + "Return a subset of XS where predicate of P return nil." + (list-filter (lambda (x) (not (funcall p x))) xs)) + +(defun list-find (p xs) + "Return the first x in XS that passes P or nil." + (-find p xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-instance? (xs) + "Return t if `XS' is a list. +Be leery of using this with things like alists. Many data structures in Elisp + are implemented using linked lists." + (listp xs)) + +(defun list-empty? (xs) + "Return t if XS are empty." + (= 0 (list-length xs))) + +(defun list-all? (p xs) + "Return t if all `XS' pass the predicate, `P'." + (-all? p xs)) + +(defun list-any? (p xs) + "Return t if any `XS' pass the predicate, `P'." + (-any? p xs)) + +(defun list-contains? (x xs) + "Return t if X is in XS using `equal'." + (-contains? xs x)) + +(defun list-xs-distinct-by? (f xs) + "Return t if all elements in XS are distinct after applying F to each." + (= (length xs) + (->> xs (-map f) set-from-list set-count))) + +;; TODO: Support dedupe. +;; TODO: Should we call this unique? Or distinct? + +;; TODO: Add tests. +(defun list-dedupe-adjacent (xs) + "Return XS without adjacent duplicates." + (prelude-assert (not (list-empty? xs))) + (list-reduce (list (list-first xs)) + (lambda (x acc) + (if (equal x (list-first acc)) + acc + (list-cons x acc))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; (when list-tests? +;; (prelude-assert +;; (= 0 +;; (list-length '()))) +;; (prelude-assert +;; (= 5 +;; (list-length '(1 2 3 4 5)))) +;; (prelude-assert +;; (= 16 +;; (list-reduce 1 (lambda (x acc) (+ x acc)) '(1 2 3 4 5)))) +;; (prelude-assert +;; (equal '(2 4 6 8 10) +;; (list-map (lambda (x) (* x 2)) '(1 2 3 4 5))))) + +(provide 'list) +;;; list.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/macros.el b/users/wpcarro/emacs/.emacs.d/wpc/macros.el new file mode 100644 index 000000000000..07b88d8f5860 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/macros.el @@ -0,0 +1,64 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;; TODO: Consider a macro solution for mimmicking OCaml's auto resolution of +;; dependencies using `load-path' and friends. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'symbol) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro macros-enable (mode) + "Helper for enabling `MODE'. +Useful in `add-hook' calls. Some modes, like `linum-mode' need to be called as +`(linum-mode 1)', so `(add-hook mode #'linum-mode)' won't work." + `#'(lambda nil (,mode 1))) + +(defmacro macros-disable (mode) + "Helper for disabling `MODE'. +Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro macros-add-hook-before-save (mode f) + "Register a hook, `F', for a mode, `MODE' more conveniently. +Usage: (macros-add-hook-before-save 'reason-mode-hook #'refmt-before-save)" + `(add-hook ,mode + (lambda () + (add-hook 'before-save-hook ,f)))) + +;; TODO: Privatize? +(defun macros--namespace () + "Return the namespace for a function based on the filename." + (->> (buffer-file-name) + f-filename + f-base)) + +(defmacro macros-comment (&rest _) + "Empty comment s-expresion where `BODY' is ignored." + `nil) + +(defmacro macros-support-file-extension (ext mode) + "Register MODE to automatically load with files ending with EXT extension. +Usage: (macros-support-file-extension \"pb\" protobuf-mode)" + (let ((extension (string-format "\\.%s\\'" ext))) + `(add-to-list 'auto-mode-alist '(,extension . ,mode)))) + +(provide 'macros) +;;; macros.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/math.el b/users/wpcarro/emacs/.emacs.d/wpc/math.el new file mode 100644 index 000000000000..9f6218fa4930 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/math.el @@ -0,0 +1,63 @@ +;;; math.el --- Math stuffs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Containing some useful mathematical functions. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst math-pi pi + "The number pi.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support all three arguments. +;; Int -> Int -> Int -> Boolean +(cl-defun math-triangle-of-power (&key base power result) + (cond + ((maybe-somes? base power result) + (error "All three arguments should not be set")) + ((maybe-somes? power result) + (message "power and result")) + ((maybe-somes? base result) + (log result base)) + ((maybe-somes? base power) + (expt base power)) + (t + (error "Two of the three arguments must be set")))) + +(defun math-mod (x y) + "Return X mod Y." + (mod x y)) + +(defun math-exp (x y) + "Return X raised to the Y." + (expt x y)) + +(defun math-round (x) + "Round X to nearest ones digit." + (round x)) + +(defun math-floor (x) + "Floor value X." + (floor x)) + +(provide 'math) +;;; math.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/maybe.el b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el new file mode 100644 index 000000000000..270ee909a8e8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el @@ -0,0 +1,79 @@ +;;; maybe.el --- Library for dealing with nil values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Inspired by Elm's Maybe library. +;; +;; For now, a Nothing value will be defined exclusively as a nil value. I'm +;; uninterested in supported falsiness in this module even at risk of going +;; against the LISP grain. +;; +;; I'm avoiding introducing a struct to handle the creation of Just and Nothing +;; variants of Maybe. Perhaps this is a mistake in which case this file would +;; be more aptly named nil.el. I may change that. Because of this limitation, +;; functions in Elm's Maybe library like andThen, which is the monadic bind for +;; the Maybe type, doesn't have a home here since we cannot compose multiple +;; Nothing or Just values without a struct or some other construct. +;; +;; Possible names for the variants of a Maybe. +;; None | Some +;; Nothing | Something +;; None | Just +;; Nil | Set +;; +;; NOTE: In Elisp, values like '() (i.e. the empty list) are aliases for nil. +;; What else in Elisp is an alias in this way? +;; Examples: +;; TODO: Provide examples of other nil types in Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar maybe--run-tests? t + "When t, run the test suite defined herein.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun maybe-nil? (x) + "Return t if X is nil." + (eq nil x)) + +(defun maybe-some? (x) + "Return t when X is non-nil." + (not (maybe-nil? x))) + +(defun maybe-nils? (&rest xs) + "Return t if all XS are nil." + (list-all? #'maybe-nil? xs)) + +(defun maybe-somes? (&rest xs) + "Return t if all XS are non-nil." + (list-all? #'maybe-some? xs)) + +(defun maybe-default (default x) + "Return DEFAULT when X is nil." + (if (maybe-nil? x) default x)) + +(defun maybe-map (f x) + "Apply F to X if X is not nil." + (if (maybe-some? x) + (funcall f x) + x)) + +(provide 'maybe) +;;; maybe.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/modeline.el b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el new file mode 100644 index 000000000000..6852bb284bc5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el @@ -0,0 +1,69 @@ +;;; modeline.el --- Customize my mode-line -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because I use EXWM, I treat my Emacs mode-line like my system bar: I need to +;; quickly check the system time, and I expect it to be at the bottom-right of +;; my Emacs frame. I used doom-modeline for awhile, which is an impressive +;; package, but it conditionally colorizes on the modeline for the active +;; buffer. So if my bottom-right window is inactive, I cannot see the time. +;; +;; My friend, @tazjin, has a modeline setup that I think is more compatible with +;; EXWM, so I'm going to base my setup off of his. + +;;; Code: + +(use-package telephone-line) + +(defun modeline-bottom-right-window? () + "Determines whether the last (i.e. +bottom-right) window of the +active frame is showing the buffer in which this function is + executed." + (let* ((frame (selected-frame)) + (right-windows (window-at-side-list frame 'right)) + (bottom-windows (window-at-side-list frame 'bottom)) + (last-window (car (seq-intersection right-windows bottom-windows)))) + (eq (current-buffer) (window-buffer last-window)))) + +(defun modeline-maybe-render-time () + "Conditionally renders the `mode-line-misc-info' string. + + The idea is to not display information like the current time, + load, battery levels on all buffers." + (when (modeline-bottom-right-window?) + (telephone-line-raw mode-line-misc-info t))) + +(defun modeline-setup () + "Render my custom modeline." + (telephone-line-defsegment telephone-line-last-window-segment () + (modeline-maybe-render-time)) + ;; Display the current EXWM workspace index in the mode-line + (telephone-line-defsegment telephone-line-exwm-workspace-index () + (when (modeline-bottom-right-window?) + (format "[%s]" exwm-workspace-current-index))) + ;; Define a highlight font for ~ important ~ information in the last + ;; window. + (defface special-highlight + '((t (:foreground "white" :background "#5f627f"))) "") + (add-to-list 'telephone-line-faces + '(highlight . (special-highlight . special-highlight))) + (setq telephone-line-lhs + '((nil . (telephone-line-position-segment)) + (accent . (telephone-line-buffer-segment)))) + (setq telephone-line-rhs + '((accent . (telephone-line-major-mode-segment)) + (nil . (telephone-line-last-window-segment + telephone-line-exwm-workspace-index)))) + (setq telephone-line-primary-left-separator 'telephone-line-tan-left + telephone-line-primary-right-separator 'telephone-line-tan-right + telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left + telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) + (telephone-line-mode 1)) + +(provide 'modeline) +;;; modeline.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/number.el b/users/wpcarro/emacs/.emacs.d/wpc/number.el new file mode 100644 index 000000000000..c8ed665b3098 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/number.el @@ -0,0 +1,142 @@ +;;; number.el --- Functions for working with numbers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; +;; Classifications of numbers: +;; - Natural: (a.k.a positive integers, counting numbers); {1, 2, 3, ... } +;; +;; - Whole: Natural Numbers, plus zero; {0, 1, 2, 3, ...} +;; +;; - Integers: Whole numbers plus all the negatives of the natural numbers; +;; {... , -2, -1, 0, 1, 2, ...} +;; +;; - Rational numbers: (a.k.a. fractions) where the top and bottom numbers are +;; integers; e.g., 1/2, 3/4, 7/2, โป4/3, 4/1. Note: The denominator cannot be +;; 0, but the numerator can be. +;; +;; - Real numbers: All numbers that can be written as a decimal. This includes +;; fractions written in decimal form e.g., 0.5, 0.75 2.35, โป0.073, 0.3333, or +;; 2.142857. It also includes all the irrational numbers such as ฯ, โ2 etc. +;; Every real number corresponds to a point on the number line. +;; +;; The functions defined herein attempt to capture the mathematical definitions +;; of numbers and their classifications as defined above. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst number-test? t + "When t, run the test suite defined herein.") + +;; TODO: What about int.el? + +;; TODO: How do we handle a number typeclass? + +(defun number-positive? (x) + "Return t if `X' is a positive number." + (> x 0)) + +(defun number-negative? (x) + "Return t if `X' is a positive number." + (< x 0)) + +;; TODO: Don't rely on this. Need to have 10.0 and 10 behave similarly. +(defun number-float? (x) + "Return t if `X' is a floating point number." + (floatp x)) + +(defun number-natural? (x) + "Return t if `X' is a natural number." + (and (number-positive? x) + (not (number-float? x)))) + +(defun number-whole? (x) + "Return t if `X' is a whole number." + (or (= 0 x) + (number-natural? x))) + +(defun number-integer? (x) + "Return t if `X' is an integer." + (or (number-whole? x) + (number-natural? (- x)))) + +;; TODO: How defensive should these guards be? Should we assert that the inputs +;; are integers before checking evenness or oddness? + +;; TODO: Look up Runar (from Unison) definition of handling zero as even or odd. + +;; TODO: How should rational numbers be handled? Lisp is supposedly famous for +;; its handling of rational numbers. +;; TODO: `calc-mode' supports rational numbers as "1:2" meaning "1/2" +;; (defun number-rational? (x)) + +;; TODO: Can or should I support real numbers? +;; (defun number-real? (x)) + +(defun number-even? (x) + "Return t if `X' is an even number." + (or (= 0 x) + (= 0 (mod x 2)))) + +(defun number-odd? (x) + "Return t if `X' is an odd number." + (not (number-even? x))) + +(defun number-dec (x) + "Subtract one from `X'. +While this function is undeniably trivial, I have unintentionally done (- 1 x) + when in fact I meant to do (- x 1) that I figure it's better for this function + to exist, and for me to train myself to reach for it and its inc counterpart." + (- x 1)) + +(defun number-inc (x) + "Add one to `X'." + (+ x 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when number-test? + (prelude-assert + (number-positive? 10)) + (prelude-assert + (number-natural? 10)) + (prelude-assert + (number-whole? 10)) + (prelude-assert + (number-whole? 0)) + (prelude-assert + (number-integer? 10)) + ;; (prelude-assert + ;; (= 120 (number-factorial 5))) + (prelude-assert + (number-even? 6)) + (prelude-refute + (number-odd? 6)) + (prelude-refute + (number-positive? -10)) + (prelude-refute + (number-natural? 10.0)) + (prelude-refute + (number-natural? -10)) + (prelude-refute + (number-natural? -10.0))) + +(provide 'number) +;;; number.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/prelude.el b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000000..824a80f8047c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el @@ -0,0 +1,145 @@ +;;; prelude.el --- My attempt at augmenting Elisp stdlib -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Some of these ideas are scattered across other modules like `fs', +;; `string-functions', etc. I'd like to keep everything modular. I still don't +;; have an answer for which items belond in `misc'; I don't want that to become +;; a dumping grounds. Ideally this file will `require' all other modules and +;; define just a handful of functions. + +;; TODO: Consider removing all dependencies from prelude.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 's) +(require 'f) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-to-string (x) + "Convert X to a string." + (format "%s" x)) + +(defun prelude-inspect (&rest args) + "Message ARGS where ARGS are any type." + (->> args + (-map #'prelude-to-string) + (apply #'s-concat) + message)) + +(defmacro prelude-call-process-to-string (cmd &rest args) + "Return the string output of CMD called with ARGS." + `(with-temp-buffer + (call-process ,cmd nil (current-buffer) nil ,@args) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Assertions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I `throw' instead of `error' here? +(defmacro prelude-assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (prelude-to-string x))) + `(unless (equal t ,x) + (error (s-concat "Assertion failed: " ,as-string))))) + +(defmacro prelude-refute (x) + "Errors unless X is nil." + (let ((as-string (prelude-to-string x))) + `(unless (equal nil ,x) + (error (s-concat "Refutation failed: " ,as-string))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Adapter functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-identity (x) + "Return X unchanged." + x) + +(defun prelude-const (x) + "Return a variadic lambda that will return X." + (lambda (&rest _) x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider packaging these into a linum-color.el package. +;; TODO: Generate the color used here from the theme. +(defvar prelude--linum-safe? nil + "Flag indicating whether it is safe to work with function `linum-mode'.") + +(defvar prelude--linum-mru-color nil + "Stores the color most recently attempted to be applied.") + +(add-hook 'linum-mode-hook + (lambda () + (setq prelude--linum-safe? t) + (when (maybe-some? prelude--linum-mru-color) + (set-face-foreground 'linum prelude--linum-mru-color)))) + +(defun prelude-set-line-number-color (color) + "Safely set linum color to `COLOR'. + +If this is called before Emacs initializes, the color will be stored in +`prelude--linum-mru-color' and applied once initialization completes. + +Why is this safe? +If `(set-face-foreground 'linum)' is called before initialization completes, +Emacs will silently fail. Without this function, it is easy to introduce +difficult to troubleshoot bugs in your init files." + (if prelude--linum-safe? + (set-face-foreground 'linum color) + (setq prelude--linum-mru-color color))) + +(defun prelude-prompt (prompt) + "Read input from user with PROMPT." + (read-string prompt)) + +(cl-defun prelude-start-process (&key name command) + "Pass command string, COMMAND, and the function name, NAME. +This is a wrapper around `start-process' that has an API that resembles +`shell-command'." + ;; TODO: Fix the bug with tokenizing here, since it will split any whitespace + ;; character, even though it shouldn't in the case of quoted string in shell. + ;; e.g. - "xmodmap -e 'one two three'" => '("xmodmap" "-e" "'one two three'") + (prelude-refute (s-contains? "'" command)) + (let* ((tokens (s-split " " command)) + (program-name (nth 0 tokens)) + (program-args (cdr tokens))) + (apply #'start-process + `(,(format "*%s<%s>*" program-name name) + ,nil + ,program-name + ,@program-args)))) + +(defun prelude-executable-exists? (name) + "Return t if CLI tool NAME exists according to the variable `exec-path'." + (let ((file (locate-file name exec-path))) + (require 'maybe) + (if (maybe-some? file) + (f-exists? file) + nil))) + +(defmacro prelude-time (x) + "Print the time it takes to evaluate X." + `(benchmark 1 ',x)) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000000..d22cace46de9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,70 @@ +;;; pulse-audio.el --- Control audio with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because everything in my configuration is turning into Elisp these days. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio--step-size 5 + "The size by which to increase or decrease the volume.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio--message (x) + "Output X to *Messages*." + (message (string-format "[pulse-audio.el] %s" x))) + +(defun pulse-audio-toggle-mute () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-mute" + :command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (pulse-audio--message "Mute toggled.")) + +(defun pulse-audio-toggle-microphone () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-microphone" + :command "pactl set-source-mute @DEFAULT_SOURCE@ toggle") + (pulse-audio--message "Microphone toggled.")) + +(defun pulse-audio-decrease-volume () + "Low the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-decrease-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ -%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume decreased.")) + +(defun pulse-audio-increase-volume () + "Raise the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-increase-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ +%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume increased.")) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/random.el b/users/wpcarro/emacs/.emacs.d/wpc/random.el new file mode 100644 index 000000000000..aa3c3071bb8f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/random.el @@ -0,0 +1,81 @@ +;;; random.el --- Functions for working with randomness -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Functions for working with randomness. Some of this code is not as +;; functional as I'd like from. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'number) +(require 'math) +(require 'series) +(require 'list) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun random-int (x) + "Return a random integer from 0 to `X'." + (random x)) + +;; TODO: Make this work with sequences instead of lists. +(defun random-choice (xs) + "Return a random element of `XS'." + (let ((ct (list-length xs))) + (list-get + (random-int ct) + xs))) + +(defun random-boolean? () + "Randonly return t or nil." + (random-choice (list t nil))) + +;; TODO: This may not work if any of these generate numbers like 0, 1, etc. +(defun random-uuid () + "Return a generated UUID string." + (let ((eight (number-dec (math-triangle-of-power :base 16 :power 8))) + (four (number-dec (math-triangle-of-power :base 16 :power 4))) + (twelve (number-dec (math-triangle-of-power :base 16 :power 12)))) + (format "%x-%x-%x-%x-%x" + (random-int eight) + (random-int four) + (random-int four) + (random-int four) + (random-int twelve)))) + +(defun random-token (length) + "Return a randomly generated hexadecimal string of LENGTH." + (->> (series/range 0 (number-dec length)) + (list-map (lambda (_) (format "%x" (random-int 15)))) + (list-join ""))) + +;; TODO: Support random-sample +;; (defun random-sample (n xs) +;; "Return a randomly sample of list XS of size N." +;; (prelude-assert (and (>= n 0) (< n (list-length xs)))) +;; (cl-labels ((do-sample +;; (n xs y ys) +;; (if (= n (set-count ys)) +;; (->> ys +;; set-to-list +;; (list-map (lambda (i) +;; (list-get i xs)))) +;; (if (set-contains? y ys) +;; (do-sample n xs (random-int (list-length xs)) ys) +;; (do-sample n xs y (set-add y ys)))))) +;; (do-sample n xs (random-int (list-length xs)) (set-new)))) + +(provide 'random) +;;; random.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/region.el b/users/wpcarro/emacs/.emacs.d/wpc/region.el new file mode 100644 index 000000000000..f915f24ec851 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/region.el @@ -0,0 +1,24 @@ +;;; region.el --- Functions for working with regions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Sometimes Emacs's function names and argument ordering is great; other times, +;; it isn't. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun region-to-string () + "Return the string in the active region." + (buffer-substring-no-properties (region-beginning) + (region-end))) + +(provide 'region) +;;; region.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scope.el b/users/wpcarro/emacs/.emacs.d/wpc/scope.el new file mode 100644 index 000000000000..267baac9fb14 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scope.el @@ -0,0 +1,107 @@ +;;; scope.el --- Work with a scope data structure -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Exposing an API for working with a scope data structure in a non-mutative +;; way. +;; +;; What's a scope? Think of a scope as a stack of key-value bindings. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'stack) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct scope scopes) + +(defun scope-new () + "Return an empty scope." + (make-scope :scopes (->> (stack-new) + (stack-push (al-new))))) + +(defun scope-flatten (xs) + "Return a flattened representation of the scope, XS. +The newest bindings eclipse the oldest." + (->> xs + scope-scopes + stack-to-list + (list-reduce (al-new) + (lambda (scope acc) + (al-merge acc scope))))) + +(defun scope-push-new (xs) + "Push a new, empty scope onto XS." + (struct-update scope + scopes + (>-> (stack-push (al-new))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-get (k xs) + "Return K from XS if it's in scope." + (->> xs + scope-flatten + (al-get k))) + +(defun scope-current (xs) + "Return the newest scope from XS." + (let ((xs-copy (copy-scope xs))) + (->> xs-copy + scope-scopes + stack-peek))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-set (k v xs) + "Set value, V, at key, K, in XS for the current scope." + (struct-update scope + scopes + (>-> (stack-map-top (>-> (al-set k v)))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-pop (xs) + "Return a new scope without the top element from XS." + (->> xs + scope-scopes + stack-pop)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-defined? (k xs) + "Return t if K is in scope of XS." + (->> xs + scope-flatten + (al-has-key? k))) + +;; TODO: Find a faster way to write aliases like this. +(defun scope-instance? (xs) + "Return t if XS is a scope struct." + (scope-p xs)) + +(provide 'scope) +;;; scope.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000000..30973607bb03 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el @@ -0,0 +1,49 @@ +;;; screen-brightness.el --- Control laptop screen brightness -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mainly just Elisp wrappers around `xbacklight`. + +;;; Code: + +;; TODO: Define some isomorphisms. E.g. int->string, string->int. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst screen-brightness-step-size 15 + "The size of the increment or decrement step for the screen's brightness.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun screen-brightness-increase () + "Increase the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-increase" + :command (string-format "xbacklight -inc %s" screen-brightness-step-size)) + (message "[screen-brightness.el] Increased screen brightness.")) + +(defun screen-brightness-decrease () + "Decrease the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-decrease" + :command (string-format "xbacklight -dec %s" screen-brightness-step-size)) + (message "[screen-brightness.el] Decreased screen brightness.")) + +(provide 'screen-brightness) +;;; screen-brightness.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scrot.el b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el new file mode 100644 index 000000000000..e7231b44fd95 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el @@ -0,0 +1,58 @@ +;;; scrot.el --- Screenshot functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; scrot is a Linux utility for taking screenshots. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'ts) +(require 'clipboard) +(require 'kbd) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst scrot-screenshot-directory "~/Downloads" + "The default directory for screenshot outputs.") + +(defconst scrot-path-to-executable "/usr/bin/scrot" + "Path to the scrot executable.") + +(defconst scrot-output-format "screenshot_%H:%M:%S_%Y-%m-%d.png" + "The format string for the output screenshot file. +See scrot's man page for more information.") + +(defun scrot--copy-image (path) + "Use xclip to copy the image at PATH to the clipboard. +This currently only works for PNG files because that's what I'm outputting" + (call-process "xclip" nil nil nil + "-selection" "clipboard" "-t" "image/png" path) + (message (string-format "[scrot.el] Image copied to clipboard!"))) + +(defun scrot-select () + "Click-and-drag to screenshot a region. +The output path is copied to the user's clipboard." + (interactive) + (let ((screenshot-path (f-join scrot-screenshot-directory + (ts-format scrot-output-format (ts-now))))) + (make-process + :name "scrot-select" + :command `(,scrot-path-to-executable "--select" ,screenshot-path) + :sentinel (lambda (proc _err) + (when (= 0 (process-exit-status proc)) + (scrot--copy-image screenshot-path)))))) + +(provide 'scrot) +;;; scrot.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/sequence.el b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el new file mode 100644 index 000000000000..9da3ba4575f9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el @@ -0,0 +1,109 @@ +;;; sequence.el --- Working with the "sequence" types -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Elisp supports a typeclass none as "sequence" which covers the following +;; types: +;; - list: '(1 2 3 4 5) +;; - vector: ["John" 27 :blue] +;; - string: "To be or not to be..." + +;; TODO: Document the difference between a "reduce" and a "fold". I.e. - reduce +;; has an initial value whereas fold uses the first element in the sequence as +;; the initial value. +;; +;; Note: This should be an approximation of Elixir's Enum protocol albeit +;; without streams. +;; +;; Elisp has done a lot of this work already and these are mostly wrapper +;; functions. +;; See the following list for reference: +;; - sequencep +;; - elt +;; - copy-sequence +;; - reverse +;; - nreverse +;; - sort +;; - seq-elt +;; - seq-length +;; - seqp +;; - seq-drop +;; - seq-take +;; - seq-take-while +;; - seq-drop-while +;; - seq-do +;; - seq-map +;; - seq-mapn +;; - seq-filter +;; - seq-remove +;; - seq-reduce +;; - seq-some +;; - seq-find +;; - seq-every-p +;; - seq-empty-p +;; - seq-count +;; - seq-sort +;; - seq-contains +;; - seq-position +;; - seq-uniq +;; - seq-subseq +;; - seq-concatenate +;; - seq-mapcat +;; - seq-partition +;; - seq-intersection +;; - seq-difference +;; - seq-group-by +;; - seq-into +;; - seq-min +;; - seq-max +;; - seq-doseq +;; - seq-let + +;;; Code: + +;; Perhaps we can provide default implementations for `filter' and `map' derived +;; from the `reduce' implementation. +;; (defprotocol sequence +;; :functions (reduce)) +;; (definstance sequence list +;; :reduce #'list-reduce +;; :filter #'list-filter +;; :map #'list-map) +;; (definstance sequence vector +;; :reduce #'vector/reduce) +;; (definstance sequence string +;; :reduce #'string) + +(defun sequence-classify (xs) + "Return the type of `XS'." + (cond + ((listp xs) 'list) + ((vectorp xs) 'vector) + ((stringp xs) 'string))) + +(defun sequence-reduce (acc f xs) + "Reduce of `XS' calling `F' on x and `ACC'." + (seq-reduce + (lambda (acc x) + (funcall f x acc)) + xs + acc)) + +;; Elixir also turned everything into a list for efficiecy reasons. + +(defun sequence-filter (p xs) + "Filter `XS' with predicate, `P'. +Returns a list regardless of the type of `XS'." + (seq-filter p xs)) + +(defun sequence-map (f xs) + "Maps `XS' calling `F' on each element. +Returns a list regardless of the type of `XS'." + (seq-map f xs)) + +(provide 'sequence) +;;; sequence.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/series.el b/users/wpcarro/emacs/.emacs.d/wpc/series.el new file mode 100644 index 000000000000..00be03a976f0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/series.el @@ -0,0 +1,93 @@ +;;; series.el --- Hosting common series of numbers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Encoding number series as I learn about them. +;; +;; These are the following series I'm interested in supporting: +;; - Fibonacci +;; - Catalan numbers +;; - Figurate number series +;; - Triangular +;; - Square +;; - Pentagonal +;; - Hexagonal +;; - Lazy-caterer +;; - Magic square +;; - Look-and-say + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun series-range (beg end) + "Create a list of numbers from `BEG' to `END'. +This is an inclusive number range." + (if (< end beg) + (list-reverse + (number-sequence end beg)) + (number-sequence beg end))) + +(defun series-fibonacci-number (i) + "Return the number in the fibonacci series at `I'." + (cond + ((= 0 i) 0) + ((= 1 i) 1) + (t (+ (series-fibonacci-number (- i 1)) + (series-fibonacci-number (- i 2)))))) + +(defun series-fibonacci (n) + "Return the first `N' numbers of the fibonaccci series starting at zero." + (if (= 0 n) + '() + (list-reverse + (list-cons (series-fibonacci-number (number-dec n)) + (list-reverse + (series-fibonacci (number-dec n))))))) + +;; TODO: Consider memoization. +(defun series-triangular-number (i) + "Return the number in the triangular series at `I'." + (if (= 0 i) + 0 + (+ i (series-triangular-number (number-dec i))))) + +;; TODO: Improve performance. +;; TODO: Consider creating a stream protocol with `stream/next' and implement +;; this using that. +(defun series-triangular (n) + "Return the first `N' numbers of a triangular series starting at 0." + (if (= 0 n) + '() + (list-reverse + (list-cons (series-triangular-number (number-dec n)) + (list-reverse + (series-triangular (number-dec n))))))) + +(defun series-catalan-number (i) + "Return the catalan number in the series at `I'." + (if (= 0 i) + 1 + (/ (number-factorial (* 2 i)) + (* (number-factorial (number-inc i)) + (number-factorial i))))) + +(defun series-catalan (n) + "Return the first `N' numbers in a catalan series." + (->> (series-range 0 (number-dec n)) + (list-map #'series-catalan-number))) + +(provide 'series) +;;; series.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/set.el b/users/wpcarro/emacs/.emacs.d/wpc/set.el new file mode 100644 index 000000000000..51c0c434f54e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/set.el @@ -0,0 +1,175 @@ +;;; set.el --- Working with mathematical sets -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; The set data structure is a collection that deduplicates its elements. + +;;; Code: + +(require 'ht) ;; friendlier API for hash-tables +(require 'dotted) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish List +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Support enum protocol for set. +;; - TODO: Prefer a different hash-table library that doesn't rely on mutative +;; code. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct set xs) + +(defconst set-enable-testing? t + "Run tests when t.") + +(defun set-from-list (xs) + "Create a new set from the list XS." + (make-set :xs (->> xs + (list-map #'dotted-new) + ht-from-alist))) + +(defun set-new (&rest args) + "Create a new set from ARGS." + (set-from-list args)) + +(defun set-to-list (xs) + "Map set XS into a list." + (->> xs + set-xs + ht-keys)) + +(defun set-add (x xs) + "Add X to set XS." + (struct-update set + xs + (lambda (table) + (let ((table-copy (ht-copy table))) + (ht-set table-copy x nil) + table-copy)) + xs)) + +;; TODO: Ensure all `*/reduce' functions share the same API. +(defun set-reduce (acc f xs) + "Return a new set by calling F on each element of XS and ACC." + (->> xs + set-to-list + (list-reduce acc f))) + +(defun set-intersection (a b) + "Return the set intersection between A and B." + (set-reduce (set-new) + (lambda (x acc) + (if (set-contains? x b) + (set-add x acc) + acc)) + a)) + +(defun set-count (xs) + "Return the number of elements in XS." + (->> xs + set-xs + ht-size)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun set-empty? (xs) + "Return t if XS has no elements in it." + (= 0 (set-count xs))) + +(defun set-contains? (x xs) + "Return t if set XS has X." + (ht-contains? (set-xs xs) x)) + +;; TODO: Prefer using `ht.el' functions for this. +(defun set-equal? (a b) + "Return t if A and B share the name members." + (ht-equal? (set-xs a) + (set-xs b))) + +(defun set-distinct? (a b) + "Return t if A and B have no shared members." + (set-empty? (set-intersection a b))) + +(defun set-superset? (a b) + "Return t if A has all of the members of B." + (->> b + set-to-list + (list-all? (lambda (x) (set-contains? x a))))) + +(defun set-subset? (a b) + "Return t if each member of set A is present in set B." + (set-superset? b a)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when set-enable-testing? + ;; set-distinct? + (prelude-assert + (set-distinct? (set-new 'one 'two 'three) + (set-new 'a 'b 'c))) + (prelude-refute + (set-distinct? (set-new 1 2 3) + (set-new 3 4 5))) + (prelude-refute + (set-distinct? (set-new 1 2 3) + (set-new 1 2 3))) + ;; set-equal? + (prelude-refute + (set-equal? (set-new 'a 'b 'c) + (set-new 'x 'y 'z))) + (prelude-refute + (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b))) + (prelude-assert + (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b 'c))) + ;; set-intersection + (prelude-assert + (set-equal? (set-new 2 3) + (set-intersection (set-new 1 2 3) + (set-new 2 3 4)))) + ;; set-{from,to}-list + (prelude-assert (equal '(1 2 3) + (->> '(1 1 2 2 3 3) + set-from-list + set-to-list))) + (let ((primary-colors (set-new "red" "green" "blue"))) + ;; set-subset? + (prelude-refute + (set-subset? (set-new "black" "grey") + primary-colors)) + (prelude-assert + (set-subset? (set-new "red") + primary-colors)) + ;; set-superset? + (prelude-refute + (set-superset? primary-colors + (set-new "black" "grey"))) + (prelude-assert + (set-superset? primary-colors + (set-new "red" "green" "blue"))) + (prelude-assert + (set-superset? primary-colors + (set-new "red" "blue")))) + ;; set-empty? + (prelude-assert (set-empty? (set-new))) + (prelude-refute (set-empty? (set-new 1 2 3))) + ;; set-count + (prelude-assert (= 0 (set-count (set-new)))) + (prelude-assert (= 2 (set-count (set-new 1 1 2 2))))) + +(provide 'set) +;;; set.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ssh.el b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000000..2e5839c04698 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el @@ -0,0 +1,65 @@ +;;; ssh.el --- When working remotely -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Configuration to make remote work easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tramp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Is "ssh" preferable to "scp"? +(setq tramp-default-method "ssh") + +;; Taken from: https://superuser.com/questions/179313/tramp-waiting-for-prompts-from-remote-shell +(setq tramp-shell-prompt-pattern "^[^$>\n]*[#$%>] *\\(\[[0-9;]*[a-zA-Z] *\\)*") + +;; Sets the value of the TERM variable to "dumb" when logging into the remote +;; host. This allows me to check for the value of "dumb" in my shell's init file +;; and control the startup accordingly. You can see in the (shamefully large) +;; commit, 0b4ef0e, that I added a check like this to my ~/.zshrc. I've since +;; switched from z-shell to fish. I don't currently have this check in +;; config.fish, but I may need to add it one day soon. +(setq tramp-terminal-type "dumb") + +;; Maximizes the tramp debugging noisiness while I'm still learning about tramp. +(setq tramp-verbose 10) + +;; As confusing as this may seem, this forces Tramp to use *my* .ssh/config +;; options, which enable ControlMaster. In other words, disabling this actually +;; enables ControlMaster. +(setq tramp-use-ssh-controlmaster-options nil) + +(defcustom ssh-hosts '("desktop" "socrates") + "List of hosts to which I commonly connect. +Note: It could be interesting to read these values from ~/.ssh-config, but + that's more than I need at the moment.") + +(defun ssh-sudo-buffer () + "Open the current buffer with sudo rights." + (interactive) + (with-current-buffer (current-buffer) + (if (s-starts-with? "/ssh:" buffer-file-name) + (message "[ssh.el] calling ssh-sudo-buffer for remote files isn't currently supported") + (find-file (format "/sudo::%s" buffer-file-name))))) + +(defun ssh-cd-home () + "Prompt for an SSH host and open a dired buffer for wpcarro on that machine." + (interactive) + (let ((machine (completing-read "Machine: " ssh-hosts))) + (find-file (format "/ssh:wpcarro@%s:~" machine)))) + +(provide 'ssh) +;;; ssh.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/stack.el b/users/wpcarro/emacs/.emacs.d/wpc/stack.el new file mode 100644 index 000000000000..c90f41e7602d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/stack.el @@ -0,0 +1,102 @@ +;;; stack.el --- Working with stacks in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; A stack is a LIFO queue. +;; The design goal here is to expose an intuitive API for working with stacks in +;; non-mutative way. +;; +;; TODO: Consider naming a Functor instance "Mappable." +;; TODO: Consider naming a Foldable instance "Reduceable." +;; +;; TODO: Consider implementing an instance for Mappable. +;; TODO: Consider implementing an instance for Reduceable. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'list) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct stack xs) + +(defun stack-new () + "Create an empty stack." + (make-stack :xs '())) + +(defun stack-from-list (xs) + "Create a new stack from the list, `XS'." + (list-reduce (stack-new) #'stack-push xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-peek (xs) + "Look at the top element of `XS' without popping it off." + (->> xs + stack-xs + list-head)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-push (x xs) + "Push `X' on `XS'." + (struct-update stack + xs + (>-> (list-cons x)) + xs)) + +;; TODO: How to return something like {(list-head xs), (list-tail xs)} in Elixir +;; TODO: How to handle popping from empty stacks? +(defun stack-pop (xs) + "Return the stack, `XS', without the top element. +Since I cannot figure out a nice way of return tuples in Elisp, if you want to +look at the first element, use `stack-peek' before running `stack-pop'." + (struct-update stack + xs + (>-> list-tail) + xs)) + +(defun stack-map-top (f xs) + "Apply F to the top element of XS." + (->> xs + stack-pop + (stack-push (funcall f (stack-peek xs))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-to-list (xs) + "Return XS as a list. +The round-trip property of `stack-from-list' and `stack-to-list' should hold." + (->> xs + stack-xs + list-reverse)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Create a macro that wraps `cl-defstruct' that automatically creates +;; things like `new', `instance?'. +(defun stack-instance? (xs) + "Return t if XS is a stack." + (stack-p xs)) + +(provide 'stack) +;;; stack.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/string.el b/users/wpcarro/emacs/.emacs.d/wpc/string.el new file mode 100644 index 000000000000..9a43f1664501 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/string.el @@ -0,0 +1,111 @@ +;;; string.el --- Library for working with strings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with string. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) +;; TODO: Resolve the circular dependency that this introduces. +;; (require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-contains? (c x) + "Return t if X is in C." + (s-contains? c x)) + +(defun string-hookify (x) + "Append \"-hook\" to X." + (s-append "-hook" x)) + +(defun string-split (y x) + "Map string X into a list of strings that were separated by Y." + (s-split y x)) + +(defun string-ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it." + (if (s-ends-with? "-hook" x) + x + (string-hookify x))) + +(defun string-format (x &rest args) + "Format template string X with ARGS." + (apply #'format (cons x args))) + +(defun string-concat (&rest strings) + "Joins `STRINGS' into onto string." + (apply #'s-concat strings)) + +(defun string-->symbol (string) + "Maps `STRING' to a symbol." + (intern string)) + +(defun string-<-symbol (symbol) + "Maps `SYMBOL' into a string." + (symbol-name symbol)) + +(defun string-prepend (prefix x) + "Prepend `PREFIX' onto `X'." + (s-concat prefix x)) + +(defun string-append (postfix x) + "Appen `POSTFIX' onto `X'." + (s-concat x postfix)) + +(defun string-surround (s x) + "Surrounds `X' one each side with `S'." + (->> x + (string-prepend s) + (string-append s))) + +;; TODO: Define a macro for defining a function and a test. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Casing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-caps->kebab (x) + "Change the casing of `X' from CAP_CASE to kebab-case." + (->> x + s-downcase + (s-replace "_" "-"))) + +(defun string-kebab->caps (x) + "Change the casing of X from CAP_CASE to kebab-case." + (->> x + s-upcase + (s-replace "-" "_"))) + +(defun string-lower->caps (x) + "Change the casing of X from lowercase to CAPS_CASE." + (->> x + s-upcase + (s-replace " " "_"))) + +(defun string-lower->kebab (x) + "Change the casing of `X' from lowercase to kebab-case." + (s-replace " " "-" x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-instance? (x) + "Return t if X is a string." + (stringp x)) + +(provide 'string) +;;; string.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/struct.el b/users/wpcarro/emacs/.emacs.d/wpc/struct.el new file mode 100644 index 000000000000..35957e834449 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/struct.el @@ -0,0 +1,86 @@ +;;; struct.el --- Helpers for working with structs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Provides new macros for working with structs. Also provides adapter +;; interfaces to existing struct macros, that should have more intuitive +;; interfaces. +;; +;; Sometimes `setf' just isn't enough. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar struct--enable-tests? t + "When t, run the test suite defined herein.") + +(defmacro struct-update (type field f xs) + "Apply F to FIELD in XS, which is a struct of TYPE. +This is immutable." + (let ((copier (->> type + symbol-name + (string-prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) (funcall ,f (,accessor copy))) + copy))) + +(defmacro struct-set (type field x xs) + "Immutably set FIELD in XS (struct TYPE) to X." + (let ((copier (->> type + symbol-name + (string-prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) ,x) + copy))) + +(defmacro struct-set! (type field x xs) + "Set FIELD in XS (struct TYPE) to X mutably. +This is an adapter interface to `setf'." + (let ((accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(progn + (setf (,accessor ,xs) ,x) + ,xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when struct--enable-tests? + (cl-defstruct dummy name age) + (defvar struct--test-dummy (make-dummy :name "Roofus" :age 19)) + (struct-set! dummy name "Doofus" struct--test-dummy) + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (let ((result (struct-set dummy name "Shoofus" struct--test-dummy))) + ;; Test the immutability of `struct-set' + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (prelude-assert (string= "Shoofus" (dummy-name result))))) + +(provide 'struct) +;;; struct.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/symbol.el b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el new file mode 100644 index 000000000000..39450caff59e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el @@ -0,0 +1,49 @@ +;;; symbol.el --- Library for working with symbols -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with symbols. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Symbols +(defun symbol-as-string (callback x) + "Treat the symbol, X, as a string while applying CALLBACK to it. +Coerce back to a symbol on the way out." + (->> x + #'symbol-name + callback + #'intern)) + +(defun symbol-to-string (x) + "Map `X' into a string." + (string-<-symbol x)) + +(defun symbol-hookify (x) + "Append \"-hook\" to X when X is a symbol." + (symbol-as-string #'string-hookify x)) + +(defun symbol-ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it when X is a symbol." + (symbol-as-string #'string-ensure-hookified x)) + +(defun symbol-instance? (x) + "Return t if X is a symbol." + (symbolp x)) + +(provide 'symbol) +;;; symbol.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/timestring.el b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el new file mode 100644 index 000000000000..a9bf64e9a459 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el @@ -0,0 +1,78 @@ +;;; timestring.el --- Quickly access timestamps in different formats -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: + +;; I was making some API calls where a URL needed a `since` parameter that of an +;; RFC 3339 encoded string. +;; +;; Because I didn't know what a RFC 3339 encoded +;; string was at the time, and because I didn't know what its format was +;; according to strftime, and because I'm most likely to forget both of these +;; things by the next time that I need something similar, I decided to write +;; this package so that I can accumulate a list of common time encodings. +;; +;; Thank you, Emacs. +;; +;; p.s. - I may turn this into a proper module and publish it. But not today. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ts) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup timestring nil + "Customize group for timestring configuration.") + +(defcustom timestring-supported-encodings + '(("RFC 3339" . "%Y-%m-%dT%H:%M:%SZ") + ;; Does anyone recognize this format? + ("IDK" . "%Y-%m-%d %H:%M:%S %z")) + "Mapping of encoding names to their format strings." + :group 'timestring) + +(defcustom timestring-supported-times + '(("yesterday" . timestring--yesterday) + ("now" . ts-now) + ("tomorrow" . timestring--tomorrow)) + "Mapping of a labels to the functions that create those time objects." + :group 'timestring) + +(defun timestring--yesterday () + "Return a time object for yesterday." + (ts-adjust 'day -1 (ts-now))) + +(defun timestring--tomorrow () + "Return a time object for yesterday." + (ts-adjust 'day +1 (ts-now))) + +(defun timestring--completing-read (label xs) + "Call `completing-read' with LABEL over the collection XS." + (alist-get (completing-read label xs) xs nil nil #'equal)) + +(defun timestring-copy-encoded-time () + "Select a common time and an encoding. + +The selected time will be encoded using the selected encoding and copied onto +your clipboard." + (interactive) + (let ((time (funcall (timestring--completing-read + "Time: " timestring-supported-times))) + (fmt (timestring--completing-read + "Encoding: " timestring-supported-encodings))) + (kill-new (ts-format fmt time)) + (message "Copied!"))) + +(provide 'timestring) +;;; timestring.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/tree.el b/users/wpcarro/emacs/.emacs.d/wpc/tree.el new file mode 100644 index 000000000000..ae5fba7950c9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tree.el @@ -0,0 +1,200 @@ +;;; tree.el --- Working with Trees -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Some friendly functions that hopefully will make working with trees cheaper +;; and therefore more appealing! +;; +;; Tree terminology: +;; - leaf: node with zero children. +;; - root: node with zero parents. +;; - depth: measures a node's distance from the root node. This implies the +;; root node has a depth of zero. +;; - height: measures the longest traversal from a node to a leaf. This implies +;; that a leaf node has a height of zero. +;; - balanced? +;; +;; Tree variants: +;; - binary: the maximum number of children is two. +;; - binary search: the maximum number of children is two and left sub-trees are +;; lower in value than right sub-trees. +;; - rose: the number of children is variable. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'list) +(require 'set) +(require 'tuple) +(require 'series) +(require 'random) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tree xs) + +(cl-defstruct node value children) + +(cl-defun tree-node (value &optional children) + "Create a node struct of VALUE with CHILDREN." + (make-node :value value + :children children)) + +(defun tree-reduce-breadth (acc f xs) + "Reduce over XS breadth-first applying F to each x and ACC (in that order). +Breadth-first traversals guarantee to find the shortest path in a graph. + They're typically more difficult to implement than DFTs and may also incur + higher memory costs on average than their depth-first counterparts.") + +;; TODO: Support :order as 'pre | 'in | 'post. +;; TODO: Troubleshoot why I need defensive (nil? node) check. +(defun tree-reduce-depth (acc f node) + "Reduce over NODE depth-first applying F to each NODE and ACC. +F is called with each NODE, ACC, and the current depth. +Depth-first traversals have the advantage of typically consuming less memory + than their breadth-first equivalents would have. They're also typically + easier to implement using recursion. This comes at the cost of not + guaranteeing to be able to find the shortest path in a graph." + (cl-labels ((do-reduce-depth + (acc f node depth) + (let ((acc-new (funcall f node acc depth))) + (if (or (maybe-nil? node) + (tree-leaf? node)) + acc-new + (list-reduce + acc-new + (lambda (node acc) + (tree-do-reduce-depth + acc + f + node + (number-inc depth))) + (node-children node)))))) + (do-reduce-depth acc f node 0))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree-height (xs) + "Return the height of tree XS.") + +;; TODO: Troubleshoot why need for (nil? node). Similar misgiving +;; above. +(defun tree-leaf-depths (xs) + "Return a list of all of the depths of the leaf nodes in XS." + (list-reverse + (tree-reduce-depth + '() + (lambda (node acc depth) + (if (or (maybe-nil? node) + (tree-leaf? node)) + (list-cons depth acc) + acc)) + xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Generators +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider parameterizing height, forced min-max branching, random +;; distributions, etc. + +;; TODO: Bail out before stack overflowing by consider branching, current-depth. + +(cl-defun tree-random (&optional (value-fn (lambda (_) nil)) + (branching-factor 2)) + "Randomly generate a tree with BRANCHING-FACTOR. + +This uses VALUE-FN to compute the node values. VALUE-FN is called with the +current-depth of the node. Useful for generating test data. Warning this +function can overflow the stack." + (cl-labels ((do-random + (d vf bf) + (make-node + :value (funcall vf d) + :children (->> (series/range 0 (number-dec bf)) + (list-map + (lambda (_) + (when (random-boolean?) + (do-random d vf bf)))))))) + (do-random 0 value-fn branching-factor))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree-instance? (tree) + "Return t if TREE is a tree struct." + (node-p tree)) + +(defun tree-leaf? (node) + "Return t if NODE has no children." + (maybe-nil? (node-children node))) + +(defun tree-balanced? (n xs) + "Return t if the tree, XS, is balanced. +A tree is balanced if none of the differences between any two depths of two leaf + nodes in XS is greater than N." + (> n (->> xs + tree-leaf-depths + set-from-list + set-count + number-dec))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst tree-enable-testing? t + "When t, test suite runs.") + +;; TODO: Create set of macros for a proper test suite including: +;; - describe (arbitrarily nestable) +;; - it (arbitrarily nestable) +;; - line numbers for errors +;; - accumulated output for synopsis +;; - do we want describe *and* it? Why not a generic label that works for both? +(when tree-enable-testing? + (let ((tree-a (tree-node 1 + (list (tree-node 2 + (list (tree-node 5) + (tree-node 6))) + (tree-node 3 + (list (tree-node 7) + (tree-node 8))) + (tree-node 4 + (list (tree-node 9) + (tree-node 10)))))) + (tree-b (tree-node 1 + (list (tree-node 2 + (list (tree-node 5) + (tree-node 6))) + (tree-node 3) + (tree-node 4 + (list (tree-node 9) + (tree-node 10))))))) + ;; instance? + (prelude-assert (tree-instance? tree-a)) + (prelude-assert (tree-instance? tree-b)) + (prelude-refute (tree-instance? '(1 2 3))) + (prelude-refute (tree-instance? "oak")) + ;; balanced? + (prelude-assert (tree-balanced? 1 tree-a)) + (prelude-refute (tree-balanced? 1 tree-b)) + (message "Tests pass!"))) + +(provide 'tree) +;;; tree.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/tuple.el b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el new file mode 100644 index 000000000000..dd8e88f5c34d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el @@ -0,0 +1,94 @@ +;;; tuple.el --- Tuple API for Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Work with cons cells with two elements with a familiar API for those who have +;; worked with tuples before. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tuple first second) + +;; Create +(defun tuple-new () + "Return an empty tuple." + (make-tuple :first nil + :second nil)) + +(defun tuple-from (a b) + "Return a new tuple from A and B." + (make-tuple :first a + :second b)) + +(defun tuple-from-dotted (dp) + "Convert dotted pair, DP, into a tuple." + (tuple-from (car dp) (cdr dp))) + +;; Read +(defun tuple-first (pair) + "Return the first element of PAIR." + (tuple-first pair)) + +(defun tuple-second (pair) + "Return the second element of PAIR." + (tuple-second pair)) + +;; Update +(defun tuple-map-each (f g pair) + "Apply F to first, G to second in PAIR." + (->> pair + (tuple-map-first f) + (tuple-map-second g))) + +(defun tuple-map (f pair) + "Apply F to PAIR." + (let ((pair-copy (copy-tuple pair))) + (funcall f pair-copy))) + +(defun tuple-map-first (f pair) + "Apply function F to the first element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-first pair-copy) (funcall f (tuple-first pair-copy))) + pair-copy)) + +(defun tuple-map-second (f pair) + "Apply function F to the second element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-second pair-copy) (funcall f (tuple-second pair-copy))) + pair-copy)) + +(defun tuple-set-first (a pair) + "Return a new tuple with the first element set as A in PAIR." + (tuple-map-first (lambda (_) a) pair)) + +(defun tuple-set-second (b pair) + "Return a new tuple with the second element set as B in PAIR." + (tuple-map-second (lambda (_) b) pair)) + +;; Delete +(defun tuple-delete-first (pair) + "Return PAIR with the first element set to nil." + (tuple-set-first nil pair)) + +(defun tuple-delete-second (pair) + "Return PAIR with the second element set to nil." + (tuple-set-second nil pair)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tuple-instance? (x) + "Return t if X is a tuple." + (tuple-p x)) + +(provide 'tuple) +;;; tuple.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/vector.el b/users/wpcarro/emacs/.emacs.d/wpc/vector.el new file mode 100644 index 000000000000..033f8de8347d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vector.el @@ -0,0 +1,85 @@ +;;; vector.el --- Working with Elisp's Vector data type -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; It might be best to think of Elisp vectors as tuples in languages like +;; Haskell or Erlang. +;; +;; Not surprisingly, this API is modelled after Elixir's Tuple API. +;; +;; Some Elisp trivia: +;; - "Array": Usually means vector or string. +;; - "Sequence": Usually means list or "array" (see above). +;; +;; It might be a good idea to think of Array and Sequence as typeclasses in +;; Elisp. This is perhaps more similar to Elixir's notion of the Enum protocol. +;; +;; Intentionally not supporting a to-list function, because tuples can contain +;; heterogenous types whereas lists should contain homogenous types. + +;;; Code: + +;; TODO: Consider supporting an alias named tuple for vector. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst vector-enable-tests? t + "When t, run the tests defined herein.") + +;; TODO: Consider labelling variadic functions like `vector-concat*' +;; vs. `vector-concat'. +(defun vector-concat (&rest args) + "Return a new vector composed of all vectors in `ARGS'." + (apply #'vconcat args)) + +;; TODO: Here's a sketch of a protocol macro being consumed. +;; (definstance monoid vector +;; :empty (lambda () [])) + +(defun vector-prepend (x xs) + "Add `X' to the beginning of `XS'." + (vector-concat `[,x] xs)) + +(defun vector-append (x xs) + "Add `X' to the end of `XS'." + (vector-concat xs `[,x])) + +(defun vector-get (i xs) + "Return the value in `XS' at index, `I'." + (aref xs i)) + +(defun vector-set (i v xs) + "Set index `I' to value `V' in `XS'. +Returns a copy of `XS' with the updates." + (let ((copy (vconcat [] xs))) + (aset copy i v) + copy)) + +(defun vector-set! (i v xs) + "Set index `I' to value `V' in `XS'. +This function mutates XS." + (aset xs i v)) + +(when vector-enable-tests? + (let ((xs [1 2 3]) + (ys [1 2 3])) + (prelude-assert (= 1 (vector-get 0 ys))) + (vector-set 0 4 ys) + (prelude-assert (= 1 (vector-get 0 ys))) + (prelude-assert (= 1 (vector-get 0 xs))) + (vector-set! 0 4 xs) + (prelude-assert (= 4 (vector-get 0 xs))))) + +;; TODO: Decide between "remove" and "delete" as the appropriate verbs. +;; TODO: Implement this. +;; (defun vector/delete (i xs) +;; "Remove the element at `I' in `XS'.") + +(provide 'vector) +;;; vector.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el new file mode 100644 index 000000000000..ce60bab149c0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el @@ -0,0 +1,129 @@ +;;; vterm-mgt.el --- Help me manage my vterm instances -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Supporting functions to instantiate vterm buffers, kill existing vterm +;; buffers, rename vterm buffers, cycle forwards and backwards through vterm +;; buffers. +;; +;; Many of the functions defined herein are intended to be bound to +;; `vterm-mode-map'. Some assertions are made to guard against calling +;; functions that are intended to be called from outside of a vterm buffer. +;; These assertions shouldn't error when the functions are bound to +;; `vterm-mode-map'. If for some reason, you'd like to bind these functions to +;; a separate keymap, caveat emptor. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'cycle) +(require 'vterm) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup vterm-mgt nil + "Customization options for `vterm-mgt'.") + +(defcustom vterm-mgt-scroll-on-focus nil + "When t, call `end-of-buffer' after focusing a vterm instance." + :type '(boolean) + :group 'vterm-mgt) + +(defconst vterm-mgt--instances (cycle-new) + "A cycle tracking all of my vterm instances.") + +(defun vterm-mgt--instance? (b) + "Return t if the buffer B is a vterm instance." + (equal 'vterm-mode (buffer-local-value 'major-mode b))) + +(defmacro vterm-mgt--assert-vterm-buffer () + "Error when the `current-buffer' is not a vterm buffer." + '(prelude-assert (vterm-mgt--instance? (current-buffer)))) + +(defun vterm-mgt-next () + "Replace the current buffer with the next item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-next vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-prev () + "Replace the current buffer with the previous item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-prev vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-instantiate () + "Create a new vterm instance. + +Prefer calling this function instead of `vterm'. This function ensures that the + newly created instance is added to `vterm-mgt--instances'. + +If however you must call `vterm', if you'd like to cycle through vterm + instances, make sure you call `vterm-mgt-populate-cycle' to allow vterm-mgt to + collect any untracked vterm instances." + (interactive) + (let ((buffer (vterm))) + (cycle-append buffer vterm-mgt--instances) + (cycle-focus-item buffer vterm-mgt--instances))) + +(defun vterm-mgt-kill () + "Kill the current buffer and remove it from `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (let ((buffer (current-buffer))) + (cycle-remove buffer vterm-mgt--instances) + (kill-buffer buffer))) + +(defun vterm-mgt-find-or-create () + "Call `switch-to-buffer' on a focused vterm instance if there is one. + +When `cycle-focused?' returns nil, focus the first item in the cycle. When +there are no items in the cycle, call `vterm-mgt-instantiate' to create a vterm +instance." + (interactive) + (if (cycle-empty? vterm-mgt--instances) + (vterm-mgt-instantiate) + (if (cycle-focused? vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)) + (progn + (cycle-jump 0 vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)))))) + +(defun vterm-mgt-rename-buffer (name) + "Rename the current buffer ensuring that its NAME is wrapped in *vterm*<...>. +This function should be called from a buffer running vterm." + (interactive "SRename vterm buffer: ") + (vterm-mgt--assert-vterm-buffer) + (rename-buffer (format "vterm<%s>" name))) + +(defun vterm-mgt-repopulate-cycle () + "Fill `vterm-mgt--instances' with the existing vterm buffers. + +If for whatever reason, the state of `vterm-mgt--instances' is corrupted and + misaligns with the state of vterm buffers in Emacs, use this function to + restore the state." + (interactive) + (setq vterm-mgt--instances + (->> (buffer-list) + (-filter #'vterm-mgt--instance?) + cycle-from-list))) + +(provide 'vterm-mgt) +;;; vterm-mgt.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000000..6030461da61b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el @@ -0,0 +1,354 @@ +;;; window-manager.el --- Functions augmenting my usage of EXWM -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; I switched to EXWM from i3, and I haven't looked back. One day I may write a +;; poem declaring my love for Emacs and EXWM. For now, I haven't the time. + +;; Wish list: +;; - TODO: Support different startup commands and layouts depending on laptop or +;; desktop. +;; - TODO: Support a Music named-workspace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'alert) +(require 'al) +(require 'prelude) +(require 'string) +(require 'cycle) +(require 'set) +(require 'kbd) +(require 'ivy-helpers) +(require 'display) +(require 'vterm-mgt) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Associate `window-purpose' window-layouts with each of these named +;; workspaces. + +;; TODO: Associate KBDs for each of these named-layouts. + +;; TODO: Decide between window-manager, exwm, or some other namespace. + +;; TODO: Support (cycle-from-list '(current previous)) to toggle back and forth +;; between most recent workspace. + +;; TODO: Support ad hoc cycle for loading a few workspaces that can be cycled +;; between. (cycle-from-list '("Project" "Workspace")) + +;; TODO: Consider supporting a workspace for Racket, Clojure, Common Lisp, +;; Haskell, Elixir, and a few other languages. These could behave very similarly +;; to repl.it, which I've wanted to have locally for awhile now. + +;; TODO: Support MRU cache of workspaces for easily switching back-and-forth +;; between workspaces. + +(cl-defstruct window-manager--named-workspace label kbd display) + +(defconst window-manager--install-kbds? t + "When t, install the keybindings to switch between named-workspaces.") + +;; TODO: Consume `cache/touch' after changing workspaces. Use this to enable +;; cycling through workspaces. + +(defconst window-manager--named-workspaces + (list (make-window-manager--named-workspace + :label "Web Browsing" + :kbd "c" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Coding" + :kbd "d" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Vertical" + :kbd "h" + :display display-4k-vertical) + (make-window-manager--named-workspace + :label "Laptop" + :kbd "p" + :display display-laptop)) + "List of `window-manager--named-workspace' structs.") + +;; Assert that no two workspaces share KBDs. +(prelude-assert (= (list-length window-manager--named-workspaces) + (->> window-manager--named-workspaces + (list-map #'window-manager--named-workspace-kbd) + set-from-list + set-count))) + +(defun window-manager--alert (x) + "Message X with a structured format." + (alert (string-concat "[exwm] " x))) + +;; Use Emacs as my primary window manager. +(use-package exwm + :config + (require 'exwm-config) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Multiple Displays + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (require 'exwm-randr) + (exwm-randr-enable) + (setq exwm-randr-workspace-monitor-plist + (->> window-manager--named-workspaces + (-map-indexed (lambda (i x) + (list i (window-manager--named-workspace-display x)))) + -flatten)) + (setq exwm-workspace-number (list-length window-manager--named-workspaces)) + (setq exwm-input-simulation-keys + ;; TODO: Consider supporting M-d and other readline style KBDs. + '(([?\C-b] . [left]) + ([?\M-b] . [C-left]) + ([?\C-f] . [right]) + ([?\M-f] . [C-right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\C-d] . [delete]) + ;; TODO: Assess whether or not this is a good idea. + ;; TODO: Ensure C-c copies. + ([?\C-c] . [C-c]))) + (exwm-enable)) + +;; Here is the code required to allow EXWM to cycle workspaces. +(defconst window-manager--workspaces + (->> window-manager--named-workspaces + cycle-from-list) + "Cycle of the my EXWM workspaces.") + +(prelude-assert + (= exwm-workspace-number + (list-length window-manager--named-workspaces))) + +(defun window-manager-next-workspace () + "Cycle forwards to the next workspace." + (interactive) + (window-manager--change-workspace (cycle-next window-manager--workspaces))) + +(defun window-manager-prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (window-manager--change-workspace (cycle-prev window-manager--workspaces))) + +;; TODO: Create friendlier API for working with EXWM. + +;; Here is the code required to toggle EXWM's modes. +(defun window-manager--line-mode () + "Switch exwm to line-mode." + (call-interactively #'exwm-input-grab-keyboard) + (window-manager--alert "Switched to line-mode")) + +(defun window-manager--char-mode () + "Switch exwm to char-mode." + (call-interactively #'exwm-input-release-keyboard) + (window-manager--alert "Switched to char-mode")) + +(defconst window-manager--modes + (cycle-from-list (list #'window-manager--char-mode + #'window-manager--line-mode)) + "Functions to switch exwm modes.") + +(defun window-manager-toggle-mode () + "Switch between line- and char- mode." + (interactive) + (with-current-buffer (window-buffer) + (when (eq major-mode 'exwm-mode) + (funcall (cycle-next window-manager--modes))))) + +;; Ensure exwm apps open in char-mode. +(add-hook 'exwm-manage-finish-hook #'window-manager--char-mode) + +;; Interface to the Linux password manager +;; TODO: Consider writing a better client for this. +(use-package ivy-pass) + +;; TODO: How do I handle this dependency? +(defconst window-manager--preferred-browser "google-chrome" + "My preferred web browser.") + +;; TODO: Consider replacing the `ivy-read' call with something like `hydra' that +;; can provide a small mode for accepting user-input. +;; TODO: Put this somewhere more diliberate. + +;; TODO: Configure the environment variables for xsecurelock so that the font is +;; smaller, different, and the glinux wallpaper doesn't show. +;; - XSECURELOCK_FONT="InputMono-Black 10" +;; - XSECURE_SAVER="" +;; - XSECURE_LOGO_IMAGE="" +;; Maybe just create a ~/.xsecurelockrc +;; TODO: Is there a shell-command API that accepts an alist and serializes it +;; into variables to pass to the shell command? +(defconst window-manager--xsecurelock + "/usr/share/goobuntu-desktop-files/xsecurelock.sh" + "Path to the proper xsecurelock executable. +The other path to xsecurelock is /usr/bin/xsecurelock, which works fine, but it +is not optimized for Goobuntu devices. Goobuntu attempts to check a user's +password using the network. When there is no network connection available, the +login attempts fail with an \"unknown error\", which isn't very helpful. To +avoid this, prefer the goobuntu wrapper around xsecurelock when on a goobuntu +device. This all relates to PAM (i.e. pluggable authentication modules).") + +(defun window-manager-logout () + "Prompt the user for options for logging out, shutting down, etc. + +The following options are supported: +- Lock +- Logout +- Suspend +- Hibernate +- Reboot +- Shutdown + +Ivy is used to capture the user's input." + (interactive) + (let* ((name->cmd `(("Lock" . + (lambda () + (shell-command window-manager--xsecurelock))) + ("Logout" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "systemctl stop lightdm")))) + ("Suspend" . + (lambda () + (shell-command "systemctl suspend"))) + ("Hibernate" . + (lambda () + (shell-command "systemctl hibernate"))) + ("Reboot" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "reboot")))) + ("Shutdown" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "shutdown now"))))))) + (funcall + (lambda () + (funcall (al-get (ivy-read "System: " (al-keys name->cmd)) + name->cmd)))))) + +(defun window-manager--label->index (label workspaces) + "Return the index of the workspace in WORKSPACES named LABEL." + (let ((index (-elem-index label (-map #'window-manager--named-workspace-label + workspaces)))) + (if index index (error (format "No workspace found for label: %s" label))))) + +(defun window-manager--register-kbd (workspace) + "Registers a keybinding for WORKSPACE struct. +Currently using super- as the prefix for switching workspaces." + (let ((handler (lambda () + (interactive) + (window-manager--switch + (window-manager--named-workspace-label workspace)))) + (key (window-manager--named-workspace-kbd workspace))) + (exwm-input-set-key + (kbd-for 'workspace key) + handler))) + +(defun window-manager--change-workspace (workspace) + "Switch EXWM workspaces to the WORKSPACE struct." + (exwm-workspace-switch + (window-manager--label->index + (window-manager--named-workspace-label workspace) + window-manager--named-workspaces)) + (window-manager--alert + (string-format "Switched to: %s" + (window-manager--named-workspace-label workspace)))) + +(defun window-manager--switch (label) + "Switch to a named workspaces using LABEL." + (cycle-focus (lambda (x) + (equal label + (window-manager--named-workspace-label x))) + window-manager--workspaces) + (window-manager--change-workspace (cycle-current window-manager--workspaces))) + +(exwm-input-set-key (kbd "C-S-f") #'window-manager-toggle-previous) + +(defun window-manager-toggle-previous () + "Focus the previously active EXWM workspace." + (interactive) + (window-manager--change-workspace + (cycle-focus-previous! window-manager--workspaces))) + +(defun window-manager--exwm-buffer? (x) + "Return t if buffer X is an EXWM buffer." + (equal 'exwm-mode (buffer-local-value 'major-mode x))) + +(defun window-manager--application-name (buffer) + "Return the name of the application running in the EXWM BUFFER. +This function asssumes that BUFFER passes the `window-manager--exwm-buffer?' +predicate." + (with-current-buffer buffer exwm-class-name)) + +;; TODO: Support disambiguating between two or more instances of the same +;; application. For instance if two `exwm-class-name' values are +;; "Google-chrome", find a encode this information in the `buffer-alist'. +(defun window-manager-switch-to-exwm-buffer () + "Use `completing-read' to focus an EXWM buffer." + (interactive) + (let* ((buffer-alist (->> (buffer-list) + (-filter #'window-manager--exwm-buffer?) + (-map + (lambda (buffer) + (cons (window-manager--application-name buffer) + buffer))))) + (label (completing-read "Switch to EXWM buffer: " buffer-alist))) + (exwm-workspace-switch-to-buffer + (al-get label buffer-alist)))) + +(when window-manager--install-kbds? + (progn + (->> window-manager--named-workspaces + (list-map #'window-manager--register-kbd)) + (window-manager--alert "Registered workspace KBDs!"))) + +(defun window-manager-current-workspace () + "Output the label of the currently active workspace." + (->> window-manager--workspaces + cycle-current + window-manager--named-workspace-label)) + +(defun window-manager-swap-workspaces () + "Prompt the user to switch the current workspace with another." + (interactive) + (let* ((selection (->> window-manager--named-workspaces + (-map #'window-manager--named-workspace-label) + (-reject + (lambda (x) + (s-equals? x (window-manager-current-workspace)))) + (completing-read + (format "Swap current workspace (i.e. \"%s\") with: " + (window-manager-current-workspace))))) + (i (-find-index (lambda (x) + (s-equals? selection (window-manager--named-workspace-label x))) + window-manager--named-workspaces))) + (exwm-workspace-swap exwm-workspace--current (elt exwm-workspace--list i)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Startup Applications in `window-manager--named-workspaces' +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(add-hook 'exwm-init-hook + (lambda () + (display-arrange-primary) + (window-manager--switch "Coding"))) + +(provide 'window-manager) +;;; window-manager.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window.el b/users/wpcarro/emacs/.emacs.d/wpc/window.el new file mode 100644 index 000000000000..ee3c109b79b4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window.el @@ -0,0 +1,41 @@ +;;; window.el --- Working with windows -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Utilities to make CRUDing windows in Emacs easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun window-find (name) + "Find a window by the NAME of the buffer it's hosting." + (let ((buffer (get-buffer name))) + (if (maybe-some? buffer) + (get-buffer-window buffer) + nil))) + +;; TODO: Find a way to incorporate these into function documentation. +(macros-comment + (window-find "*scratch*")) + +(defun window-delete (window) + "Delete the WINDOW reference." + (delete-window window)) + +(provide 'window) +;;; window.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el new file mode 100644 index 000000000000..025ef9aab988 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el @@ -0,0 +1,72 @@ +;;; wpc-clojure.el --- My Clojure preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package clojure-mode + :config + ;; from Ryan Schmukler: + (setq cljr-magic-require-namespaces + '(("io" . "clojure.java.io") + ("sh" . "clojure.java.shell") + ("jdbc" . "clojure.java.jdbc") + ("set" . "clojure.set") + ("time" . "java-time") + ("str" . "cuerdas.core") + ("path" . "pathetic.core") + ("walk" . "clojure.walk") + ("zip" . "clojure.zip") + ("async" . "clojure.core.async") + ("component" . "com.stuartsierra.component") + ("http" . "clj-http.client") + ("url" . "cemerick.url") + ("sql" . "honeysql.core") + ("csv" . "clojure.data.csv") + ("json" . "cheshire.core") + ("s" . "clojure.spec.alpha") + ("fs" . "me.raynes.fs") + ("ig" . "integrant.core") + ("cp" . "com.climate.claypoole") + ("re-frame" . "re-frame.core") + ("rf" . "re-frame.core") + ("re" . "reagent.core") + ("reagent" . "reagent.core") + ("u.core" . "utopia.core") + ("gen" . "clojure.spec.gen.alpha")))) + +(use-package cider + :config + (general-define-key + :keymaps 'cider-repl-mode-map + "C-l" #'cider-repl-clear-buffer + "C-u" #'kill-whole-line + "<up>" #'cider-repl-previous-input + "<down>" #'cider-repl-next-input) + (general-define-key + :keymaps 'clojure-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'cider-eval-defun-at-point + "X" #'cider-eval-buffer + "d" #'cider-symbol-at-point) + (setq cider-prompt-for-symbol nil)) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el new file mode 100644 index 000000000000..03535621cf2a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el @@ -0,0 +1,42 @@ +;;; wpc-company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; autocompletion client +(use-package company + :config + (general-define-key + :keymaps 'company-active-map + "C-j" #'company-select-next + "C-n" #'company-select-next + "C-k" #'company-select-previous + "C-p" #'company-select-previous + "C-d" #'company-show-doc-buffer) + (setq company-tooltip-align-annotations t) + (setq company-idle-delay 0) + (setq company-show-numbers t) + (setq company-minimum-prefix-length 2) + (setq company-dabbrev-downcase nil + company-dabbrev-ignore-case t) + (global-company-mode)) + +(provide 'wpc-company) +;;; wpc-company.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el new file mode 100644 index 000000000000..bd2805c7369c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el @@ -0,0 +1,53 @@ +;;; wpc-dired.el --- My dired preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; File management in Emacs, if learned and configured properly, should be +;; capable to reduce my dependency on the terminal. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (require 'dired) + (setq dired-recursive-copies 'always + dired-recursive-deletes 'top + dired-dwim-target t) + (setq dired-listing-switches "-la --group-directories-first") + (general-define-key + :keymaps 'dired-mode-map + :states '(normal) + ;; Overriding some KBDs defined in the evil-collection module. + "o" #'dired-find-file-other-window + "<SPC>" nil ;; This unblocks some of my leader-prefixed KBDs. + "s" nil ;; This unblocks my window-splitting KBDs. + "c" #'find-file + "f" #'project-find-file + "-" (lambda () (interactive) (find-alternate-file ".."))) + (general-add-hook 'dired-mode-hook + (list (macros-enable dired-hide-details-mode) + #'auto-revert-mode))) + +(progn + (require 'locate) + (general-define-key + :keymaps 'locate-mode-map + :states 'normal + "o" #'dired-find-file-other-window)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el new file mode 100644 index 000000000000..0b5e3917139e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el @@ -0,0 +1,28 @@ +;;; wpc-elixir.el --- Elixir / Erland configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; My preferences for working with Elixir / Erlang projects + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package elixir-mode + :config + (macros-add-hook-before-save 'elixir-mode-hook #'elixir-format)) + +(provide 'wpc-elixir) +;;; wpc-elixir.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el new file mode 100644 index 000000000000..1f32ce51361e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el @@ -0,0 +1,18 @@ +;;; wpc-flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; wpc-flycheck.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el new file mode 100644 index 000000000000..d73249334f89 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el @@ -0,0 +1,48 @@ +;;; wpc-golang.el --- Tooling preferences for Go -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Tooling support for golang development. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support jumping to go source code for fmt.Println, etc. + +;; I'm unsure if this belongs in wpc-golang.el because it's a generic setting, +;; but because go is the first languages I've encountered that enforces tab +;; usage (I think) I'm configuring it. +(setq-default tab-width 4) + +(use-package go-mode + :config + (setq gofmt-command "goimports") + ;; TODO: Consider configuring `xref-find-definitions' to use `godef-jump' + ;; instead of shadowing the KBD here. + (general-define-key + :states '(normal) + :keymaps '(go-mode-map) + "M-." #'godef-jump) + ;; Support calling M-x `compile'. + (add-hook 'go-mode-hook (lambda () + (set (make-local-variable 'compile-command) + "go build -v"))) + (macros-add-hook-before-save 'go-mode-hook #'gofmt-before-save)) + +(provide 'wpc-golang) +;;; wpc-golang.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el new file mode 100644 index 000000000000..f9ed8552e0be --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el @@ -0,0 +1,62 @@ +;;; wpc-haskell.el --- My Haskell preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; font-locking, glyph support, etc +(use-package haskell-mode + :config + (macros-add-hook-before-save 'haskell-mode #'haskell-align-imports)) + +;; LSP support +(use-package lsp-haskell + :after (haskell-mode) + :config + (setq lsp-haskell-process-path-hie "hie-wrapper") + (add-hook 'haskell-mode-hook #'lsp-haskell-enable) + (add-hook 'haskell-mode-hook #'flycheck-mode)) + +;; Test toggling +(defun wpc-haskell-module->test () + "Jump from a module to a test." + (let ((filename (->> buffer-file-name + (s-replace "/src/" "/test/") + (s-replace ".hs" "Test.hs") + find-file))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test->module () + "Jump from a test to a module." + (let ((filename (->> buffer-file-name + (s-replace "/test/" "/src/") + (s-replace "Test.hs" ".hs")))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test<->module () + "Toggle between test and module in Haskell." + (interactive) + (if (s-contains? "/src/" buffer-file-name) + (wpc-haskell-module->test) + (wpc-haskell-test->module))) + +(provide 'wpc-haskell) +;;; wpc-haskell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el new file mode 100644 index 000000000000..da84df66d409 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el @@ -0,0 +1,99 @@ +;;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This module hosts my Javascript tooling preferences. This also includes +;; tooling for TypeScript and other frontend tooling. Perhaps this module will +;; change names to more accurately reflect that. +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Constants +(defconst wpc-javascript--js-hooks + '(js-mode-hook + web-mode-hook + typescript-mode-hook + js2-mode-hook + rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc-javascript--frontend-hooks + (-insert-at 0 'css-mode-hook wpc-javascript--js-hooks) + "All of the commonly user hooks for frontend development.") + +;; frontend indentation settings +(setq typescript-indent-level 2 + js-indent-level 2 + css-indent-offset 2) + +;; Flow for Javascript +(use-package add-node-modules-path + :config + (general-add-hook wpc-javascript--js-hooks #'add-node-modules-path)) + +(use-package web-mode + :mode "\\.html\\'" + :config + (setq web-mode-css-indent-offset 2) + (setq web-mode-code-indent-offset 2) + (setq web-mode-markup-indent-offset 2)) + +;; JSX highlighting +(use-package rjsx-mode + :config + (general-unbind rjsx-mode-map "<" ">" "C-d") + (general-nmap + :keymaps 'rjsx-mode-map + "K" #'flow-minor-type-at-pos) + (setq js2-mode-show-parse-errors nil + js2-mode-show-strict-warnings nil)) + +(progn + (defun wpc-javascript-tide-setup () + (interactive) + (tide-setup) + (flycheck-mode 1) + (setq flycheck-check-syntax-automatically '(save mode-enabled)) + (eldoc-mode 1) + (tide-hl-identifier-mode 1) + (company-mode 1)) + (use-package tide + :config + (add-hook 'typescript-mode-hook #'wpc-javascript-tide-setup)) + (require 'web-mode) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode)) + (add-hook 'web-mode-hook + (lambda () + (when (string-equal "tsx" (f-ext buffer-file-name)) + (wpc-javascript-tide-setup)))) + (flycheck-add-mode 'typescript-tslint 'web-mode)) + +;; JS autoformatting +(use-package prettier-js + :config + (general-add-hook wpc-javascript--frontend-hooks #'prettier-js-mode)) + +;; Support Elm +(use-package elm-mode + :config + (add-hook 'elm-mode-hook #'elm-format-on-save-mode)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el new file mode 100644 index 000000000000..f4f8c6931582 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el @@ -0,0 +1,116 @@ +;;; wpc-lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; parent (up) +;; child (down) +;; prev-sibling (left) +;; next-sibling (right) + +;;; Code: + +;; TODO: Consider having a separate module for each LISP dialect. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst wpc-lisp--hooks + '(lisp-mode-hook + emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook + racket-mode-hook) + "List of LISP modes.") + +(use-package sly + :config + (setq inferior-lisp-program "sbcl") + (general-define-key + :keymaps 'sly-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'sly-eval-defun + "X" #'sly-eval-buffer + "d" #'sly-describe-symbol)) + +(use-package rainbow-delimiters + :config + (general-add-hook wpc-lisp--hooks #'rainbow-delimiters-mode)) + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'racket-send-definition + "X" #'racket-run + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst wpc-lisp--lispyville-key-themes + '(c-w + operators + text-objects + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc-lisp--hooks #'lispyville-mode) + (lispyville-set-key-theme wpc-lisp--lispyville-key-themes) + (progn + (general-define-key + :keymaps 'lispyville-mode-map + :states 'motion + ;; first unbind + "M-h" nil + "M-l" nil) + (general-define-key + :keymaps 'lispyville-mode-map + :states 'normal + ;; first unbind + "M-j" nil + "M-k" nil + ;; second rebind + "C-s-h" #'lispyville-drag-backward + "C-s-l" #'lispyville-drag-forward + "C-s-e" #'lispyville-end-of-defun + "C-s-a" #'lispyville-beginning-of-defun))) + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :prefix "<SPC>" + :states 'normal + "x" #'eval-defun + "X" #'eval-buffer + "d" (lambda () + (interactive) + (with-current-buffer (current-buffer) + (helpful-function (symbol-at-point))))) + +(provide 'wpc-lisp) +;;; wpc-lisp.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el new file mode 100644 index 000000000000..574162fbfd82 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el @@ -0,0 +1,315 @@ +;;; wpc-misc.el --- Hosting miscellaneous configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; URL: https://git.wpcarro.dev/wpcarro/briefcase + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'project) +(require 'f) +(require 'dash) +(require 'constants) +(require 'region) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq display-time-string-forms + '((format-time-string "%H:%M %a %b %d"))) +(display-time-mode 1) + +;; Remove the boilerplate in the *scratch* buffer +(setq initial-scratch-message "") + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file (f-join user-emacs-directory "custom.el")) +(load custom-file 'noerror) + +;; integrate Emacs with X11 clipboard +(setq select-enable-primary t) +(setq select-enable-clipboard t) +(general-def 'insert + "s-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +;; transparently edit compressed files +(auto-compression-mode t) + +;; autowrap when over the fill-column +(setq-default auto-fill-function #'do-auto-fill) + +;; link to Emacs source code +;; TODO: Update this link. +(setq find-function-C-source-directory + "~/Dropbox/programming/emacs/src") + +;; change emacs prompts from "yes or no" -> "y or n" +(fset 'yes-or-no-p 'y-or-n-p) + +;; open photos in Emacs +(auto-image-file-mode 1) + +;; disable line-wrapping +(setq-default truncate-lines 1) + +;; shell file indentation +(setq sh-basic-offset 2) +(setq sh-indentation 2) + +;; Emacs library that interfaces with my Linux password manager. +(use-package password-store) + +(use-package vterm + :config + (general-define-key + :keymaps '(vterm-mode-map) + :states '(insert) + "C-S-v" #'vterm-yank) + (general-define-key + :keymaps '(vterm-mode-map) + :states '(normal) + "K" #'evil-scroll-line-up + "J" #'evil-scroll-line-down + "C-b" #'evil-scroll-page-up + "C-f" #'evil-scroll-page-down)) + +;; Use en Emacs buffer as a REST client. +;; For more information: http://emacsrocks.com/e15.html +(use-package restclient) + +;; Run `package-lint' before publishing to MELPA. +(use-package package-lint) + +;; Parser combinators in Elisp. +(use-package parsec) + +;; disable company mode when editing markdown +;; TODO: move this out of wpc-misc.el and into a later file to call +;; `(disable company-mode)' +(use-package markdown-mode + :config + ;; TODO: Add assertion that pandoc is installed and it is accessible from + ;; Emacs. + (setq markdown-command "pandoc") + (setq markdown-split-window-direction 'right) + ;; (add-hook 'markdown-mode-hook #'markdown-live-preview-mode) + ) + +(use-package alert) + +(use-package refine) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit + :config + (add-hook 'git-commit-setup-hook + (lambda () + (company-mode -1) + (flyspell-mode 1))) + (setq magit-display-buffer-function + #'magit-display-buffer-fullframe-status-v1)) + +(use-package magit-popup) + +;; http +(use-package request) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; If called from an existing helpful-mode buffer, reuse that buffer; otherwise, +;; call `pop-to-buffer'. +(setq helpful-switch-buffer-function + (lambda (buffer-or-name) + (if (eq major-mode 'helpful-mode) + (switch-to-buffer buffer-or-name) + (pop-to-buffer buffer-or-name)))) + +;; Emacs integration with direnv +(use-package direnv + :config + (direnv-mode)) + +;; Superior Elisp library for working with dates and times. +;; TODO: Put this where my other installations for dash.el, s.el, a.el, and +;; other utility Elisp libraries are located. +(use-package ts) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; configure ibuffer +(setq ibuffer-default-sorting-mode 'major-mode) + +;; config Emacs to use $PATH values +(use-package exec-path-from-shell + :if (memq window-system '(mac ns)) + :config + (exec-path-from-shell-initialize)) + +;; Emacs autosave, backup, interlocking files +(setq auto-save-default nil + make-backup-files nil + create-lockfiles nil) + +;; ensure code wraps at 80 characters by default +(setq-default fill-column constants-fill-column) + +(put 'narrow-to-region 'disabled nil) + +;; trim whitespace on save +(add-hook 'before-save-hook #'delete-trailing-whitespace) + +;; call `git secret hide` after saving secrets.json +(add-hook 'after-save-hook + (lambda () + (when (f-equal? (buffer-file-name) + (f-join constants-briefcase "secrets.json")) + (shell-command "git secret hide")))) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(setq ns-use-native-fullscreen nil) + +(use-package yasnippet + :config + (setq yas-snippet-dirs (list (f-join user-emacs-directory "snippets"))) + (yas-global-mode 1)) + +(use-package projectile + :config + (projectile-mode t)) + +;; TODO: Consider moving this into a briefcase.el module. +(defun wpc-misc--briefcase-find (dir) + "Find the default.nix nearest to DIR." + ;; I use 'vc only at the root of my monorepo because 'transient doesn't use my + ;; .gitignore, which slows things down. Ideally, I could write a version that + ;; behaves like 'transient but also respects my monorepo's .gitignore and any + ;; ancestor .gitignore files. + (if (f-equal? constants-briefcase dir) + (cons 'vc dir) + (when (f-ancestor-of? constants-briefcase dir) + (if (f-exists? (f-join dir "default.nix")) + (cons 'transient dir) + (wpc-misc--briefcase-find (f-parent dir)))))) + +(add-to-list 'project-find-functions #'wpc-misc--briefcase-find) + +(defun wpc-misc-pkill (name) + "Call the pkill executable using NAME as its argument." + (interactive "sProcess name: ") + (call-process "pkill" nil nil nil name)) + +(use-package deadgrep + :config + (general-define-key + :keymaps 'deadgrep-mode-map + :states 'normal + "o" #'deadgrep-visit-result-other-window) + (setq-default deadgrep--context '(0 . 3)) + (defun wpc-misc-deadgrep-region () + "Run a ripgrep search on the active region." + (interactive) + (deadgrep (region-to-string))) + (defun wpc-misc-deadgrep-dwim () + "If a region is active, use that as the search, otherwise don't." + (interactive) + (with-current-buffer (current-buffer) + (if (region-active-p) + (setq deadgrep--additional-flags '("--multiline")) + (wpc-misc-deadgrep-region) + (call-interactively #'deadgrep)))) + (advice-add + 'deadgrep--format-command + :filter-return + (lambda (cmd) + (replace-regexp-in-string + "^rg " "rg --hidden " cmd)))) + +;; TODO: Do I need this when I have swiper? +(use-package counsel) + +(use-package counsel-projectile) + +;; search Google, Stackoverflow from within Emacs +(use-package engine-mode + :config + (defengine google + "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s" + :keybinding "g") + (defengine stack-overflow + "https://stackoverflow.com/search?q=%s" + :keybinding "s")) + +;; EGlot (another LSP client) +(use-package eglot) + +;; Microsoft's Debug Adapter Protocol (DAP) +(use-package dap-mode + :after lsp-mode + :config + (dap-mode 1) + (dap-ui-mode 1)) + +;; Microsoft's Language Server Protocol (LSP) +(use-package lsp-ui + :config + (add-hook 'lsp-mode-hook #'lsp-ui-mode)) + +(use-package company-lsp + :config + (push 'company-lsp company-backends)) + +;; Wilfred/suggest.el - Tool for discovering functions basesd on declaring your +;; desired inputs and outputs. +(use-package suggest) + +;; Malabarba/paradox - Enhances the `list-packages' view. +(use-package paradox + :config + (paradox-enable)) + +;; Start the Emacs server +(when (not (server-running-p)) + (server-start)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el new file mode 100644 index 000000000000..a555e4621a1f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el @@ -0,0 +1,72 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Configuration to support working with Nix. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'device) +(require 'constants) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package nix-mode + :mode "\\.nix\\'") + +;; TODO(wpcarro): Ensure the sub-process can resolve <briefcase>. +(defun wpc-nix-rebuild-emacs () + "Use nix-env to rebuild wpcarros-emacs." + (interactive) + (let* ((pname (format "nix-build <briefcase/emacs.nixos>")) + (bname (format "*%s*" pname))) + (start-process pname bname + "nix-env" + "-I" (format "briefcase=%s" constants-briefcase) + "-f" "<briefcase>" "-iA" "emacs.nixos") + (display-buffer bname))) + +(defun wpc-nix-sly-from-briefcase (attr) + "Start a Sly REPL configured using the derivation pointed at by ATTR. + + The derivation invokes nix.buildLisp.sbclWith and is built asynchronously. + The build output is included in the error thrown on build failures." + (interactive "sAttribute: ") + (lexical-let* ((outbuf (get-buffer-create (format "*briefcase-out/%s*" attr))) + (errbuf (get-buffer-create (format "*briefcase-errors/%s*" attr))) + (expression (format "let briefcase = import <briefcase> {}; in briefcase.third_party.depot.nix.buildLisp.sbclWith [ briefcase.%s ]" attr)) + (command (list "nix-build" "-E" expression))) + (message "Acquiring Lisp for <briefcase>.%s" attr) + (make-process :name (format "nix-build/%s" attr) + :buffer outbuf + :stderr errbuf + :command command + :sentinel + (lambda (process event) + (unwind-protect + (pcase event + ("finished\n" + (let* ((outpath (s-trim (with-current-buffer outbuf + (buffer-string)))) + (lisp-path (s-concat outpath "/bin/sbcl"))) + (message "Acquired Lisp for <briefcase>.%s at %s" + attr lisp-path) + (sly lisp-path))) + (_ (with-current-buffer errbuf + (error "Failed to build '%s':\n%s" attr + (buffer-string))))) + (kill-buffer outbuf) + (kill-buffer errbuf)))))) + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el new file mode 100644 index 000000000000..bed4dd067615 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el @@ -0,0 +1,40 @@ +;;; wpc-org.el --- My org preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package org + :config + (evil-set-initial-state 'org-mode 'normal) + (general-add-hook 'org-mode-hook + (list (macros-disable linum-mode) + (macros-disable company-mode))) + (setq org-startup-folded nil) + (setq org-todo-keywords '((sequence "TODO" "BLOCKED" "DONE"))) + (general-unbind 'normal org-mode-map "M-h" "M-j" "M-k" "M-l")) + +(use-package org-bullets + :config + (general-add-hook 'org-mode-hook (macros-enable org-bullets-mode))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el new file mode 100644 index 000000000000..3a363df8ec9b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el @@ -0,0 +1,33 @@ +;;; wpc-package.el --- My package configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) + +;; Even though we're packaging our Emacs with Nix, having MELPA registered is +;; helpful to ad-hoc test out packages before declaratively adding them to +;; emacs/default.nix. +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + ;; TODO: Consider removing this to improve initialization speed. + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +;; TODO: Consider removing this, since I'm requiring general.el in individual +;; modules. +(use-package general) + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el new file mode 100644 index 000000000000..b891a7df6912 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el @@ -0,0 +1,20 @@ +;;; wpc-prolog.el --- For Prologging things -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Code configuring my Prolog work. + +;;; Code: + +(require 'macros) + +;; TODO: Notice that the .pl extension conflicts with Perl files. This may +;; become a problem should I start working with Perl. +(macros-support-file-extension "pl" prolog-mode) + +(provide 'wpc-prolog) +;;; wpc-prolog.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el new file mode 100644 index 000000000000..cd30f3ea3739 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el @@ -0,0 +1,25 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package py-yapf + :config + (add-hook 'python-mode-hook #'py-yapf-enable-on-save)) + +(provide 'wpc-python) +;;; wpc-python.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el new file mode 100644 index 000000000000..396d6349ae6a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el @@ -0,0 +1,48 @@ +;;; wpc-rust.el --- Support Rust language -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Supports my Rust work. +;; +;; Dependencies: +;; - `rustup` +;; - `rustup component add rust-src` +;; - `rustup toolchain add nightly && cargo +nightly install racer` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package racer + :config + (setq rust-sysroot (->> "~/.cargo/bin/rustc --print sysroot" + shell-command-to-string + s-trim-right)) + (setq racer-rust-src-path (f-join rust-sysroot "lib/rustlib/src/rust/src")) + (add-hook 'racer-mode-hook #'eldoc-mode)) + +(use-package rust-mode + :config + (add-hook 'rust-mode-hook #'racer-mode) + (macros-add-hook-before-save 'rust-mode-hook #'rust-format-buffer) + (define-key rust-mode-map + (kbd "TAB") + #'company-indent-or-complete-common) + (define-key rust-mode-map + (kbd "M-d") + #'racer-describe)) + +(provide 'wpc-rust) +;;; wpc-rust.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el new file mode 100644 index 000000000000..855f234b281d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el @@ -0,0 +1,32 @@ +;;; wpc-shell.el --- POSIX Shell scripting support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Helpers for my shell scripting. Includes bash, zsh, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'zle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package flymake-shellcheck + :commands flymake-shellcheck-load + :init + (add-hook 'sh-mode-hook #'flymake-shellcheck-load) + (add-hook 'sh-mode-hook #'zle-minor-mode)) + +(use-package fish-mode) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el new file mode 100644 index 000000000000..fd025ddd8fb3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el @@ -0,0 +1,181 @@ +;;; wpc-ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'constants) +(require 'prelude) +(require 'al) +(require 'fonts) +(require 'colorscheme) +(require 'device) +(require 'laptop-battery) +(require 'modeline) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; line height +(setq-default line-spacing 0) + +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b")))) + +;; Ensure that buffers update when their contents change on disk. +(global-auto-revert-mode t) + +;; smooth scrolling settings +(setq scroll-step 1 + scroll-conservatively 10000) + +;; clean up modeline +(use-package diminish + :config + (diminish 'emacs-lisp-mode "elisp") + (diminish 'evil-commentary-mode) + (diminish 'flycheck-mode) + (diminish 'auto-revert-mode) + (diminish 'which-key-mode) + (diminish 'yas-minor-mode) + (diminish 'lispyville-mode) + (diminish 'undo-tree-mode) + (diminish 'company-mode) + (diminish 'projectile-mode) + (diminish 'eldoc-mode) + ;; This is how to diminish `auto-fill-mode'. + (diminish 'auto-fill-function) + (diminish 'counsel-mode) + (diminish 'ivy-mode)) + +;; TODO: Further customize `mode-line-format' variable. +(delete 'mode-line-modes mode-line-format) +(delete '(vc-mode vc-mode) mode-line-format) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; set default buffer for Emacs +(setq initial-buffer-choice constants-current-project) + +;; premium Emacs themes +(use-package doom-themes + :config + (setq doom-themes-enable-bold t + doom-themes-enable-italic t) + (doom-themes-visual-bell-config) + (doom-themes-org-config)) + +;; kbd discovery +(use-package which-key + :config + (setq which-key-idle-delay 0.25) + (which-key-mode)) + +;; completion framework +(use-package ivy + :config + (counsel-mode t) + (ivy-mode t) + ;; Remove preceding "^" from ivy prompts + (setq ivy-initial-inputs-alist nil) + ;; prefer using `helpful' variants + (progn + (setq counsel-describe-function-function #'helpful-callable) + (setq counsel-describe-variable-function #'helpful-variable)) + (general-define-key + :keymaps '(ivy-minibuffer-map ivy-switch-buffer-map) + ;; prev + "C-k" #'ivy-previous-line + "<backtab>" #'ivy-previous-line + ;; next + "C-j" #'ivy-next-line + "<tab>" #'ivy-next-line)) + +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (prescient-persist-mode 1)) + +;; all-the-icons +(use-package all-the-icons + :config + (when (not constants-ci?) + (unless (f-exists? "~/.local/share/fonts/all-the-icons.ttf") + (all-the-icons-install-fonts t)))) + +;; icons for Ivy +(use-package all-the-icons-ivy + :after (ivy all-the-icons) + :config + (all-the-icons-ivy-setup)) + +;; disable menubar +(menu-bar-mode -1) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over `constants-fill-column' characters long +(use-package whitespace + :config + ;; TODO: This should change depending on the language and project. For + ;; example, Google Java projects prefer 100 character width instead of 80 + ;; character width. + (setq whitespace-line-column constants-fill-column) + (setq whitespace-style '(face lines-tail)) + (add-hook 'prog-mode-hook #'whitespace-mode)) + +;; dirname/filename instead of filename<dirname> +(setq uniquify-buffer-name-style 'forward) + +;; highlight matching parens, brackets, etc +(show-paren-mode 1) + +;; hide the scroll-bars in the GUI +(scroll-bar-mode -1) + +;; TODO: Learn how to properly integrate this with dunst or another system-level +;; notification program. +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +;; TODO: Should `device-work-laptop?' be a function or a constant that gets set +;; during initialization? +(when (device-work-laptop?) + (laptop-battery-display)) + +(if window-system + (progn + (fonts-whitelist-set "JetBrainsMono") + (fonts-enable-ligatures) + (colorscheme-whitelist-set 'doom-solarized-light) + ;; the doom-acario-dark theme uses "Monospace Serif" as the font for + ;; comments, and I'd prefer JetBrainsMono (no italics). + (set-face-attribute font-lock-comment-face nil + :family "JetBrainsMono" + :slant 'normal)) + (load-theme 'wombat)) + +(modeline-setup) + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/zle.el b/users/wpcarro/emacs/.emacs.d/wpc/zle.el new file mode 100644 index 000000000000..0e0baad2d974 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/zle.el @@ -0,0 +1,92 @@ +;;; zle.el --- Functions to mimmick my ZLE KBDs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This is primarily for personal use. The keybindings that I choose are those +;; that feel slightly mnemonic while also not shadowing important bindings. +;; It's quite possible that our tastes will differ here. +;; +;; All of these keybindings are intended to shave off milliseconds off your +;; typing. I don't expect these numbers to sum up to a meaningful amount. The +;; primary reason that I wrote this, is that it introduces a small amount of +;; structural editing to my workflow. I've been using these exact keybindings +;; on the command line, and I find them subtely delightful to use. So much so +;; that I decided to bring them to my Emacs configuration. +;; +;; ZLE is the Z-shell line editor. I have some KBDs and functions that I often +;; want in Emacs. +;; +;; Usage: +;; Consider running `(zle-minor-mode)' to run this globally. Depending on your +;; configuration, it could be non-disruptive, disruptive, or extremely +;; disruptive. + +;;; Code: + +;; subshell (C-j) +(defun zle-subshell () + "Insert the characters necessary to create a subshell." + (interactive) + (insert-char ?$) + (insert-char ?\() + (save-excursion + (insert-char ?\)))) + +;; variable (C-v) +(defun zle-variable () + "Insert the characters to reference a variable." + (interactive) + (insert-char ?$) + (insert-char ?{) + (save-excursion + (insert-char ?}))) + +;; 2x dash (C-M--) +(defun zle-dash-dash () + "Insert the characters for flags with 2x dashes." + (interactive) + (insert-char ? ) + (insert-char ?-) + (insert-char ?-)) + +;; 1x quotes (M-') +(defun zle-single-quote () + "Insert the characters to quickly create single quotes." + (interactive) + (insert-char ? ) + (insert-char ?') + (save-excursion + (insert-char ?'))) + +;; 2x quotes (M-") +(defun zle-double-quote () + "Insert the characters to quickly create double quotes." + (interactive) + (insert-char ? ) + (insert-char ?\") + (save-excursion + (insert-char ?\"))) + +(defvar zle-kbds + (let ((map (make-sparse-keymap))) + (bind-keys :map map + ("C-j" . zle-subshell) + ("C-v" . zle-variable) + ("C-M--" . zle-dash-dash) + ("M-'" . zle-single-quote) + ("M-\"" . zle-double-quote)) + map) + "Keybindings shaving milliseconds off of typing.") + +(define-minor-mode zle-minor-mode + "A minor mode mirroring my ZLE keybindings." + :init-value nil + :lighter " zle" + :keymap zle-kbds) + +(provide 'zle) +;;; zle.el ends here diff --git a/users/wpcarro/emacs/README.md b/users/wpcarro/emacs/README.md new file mode 100644 index 000000000000..b4b16b32853f --- /dev/null +++ b/users/wpcarro/emacs/README.md @@ -0,0 +1,13 @@ +# Emacs + +Emacs is one of a handful software projects that I highly value. I consider it +as central to my workflow as `git` and `nix`. + +## Installing + +If you already have `briefcase` on your local file system, run the following +from the top-level `briefcase` directory: + +```shell +$ nix-build -f . -iA emacs.nixos +``` diff --git a/users/wpcarro/emacs/default.nix b/users/wpcarro/emacs/default.nix new file mode 100644 index 000000000000..4c50d3294cfa --- /dev/null +++ b/users/wpcarro/emacs/default.nix @@ -0,0 +1,191 @@ +{ pkgs, depot, ... }: + +let + inherit (builtins) path; + inherit (depot.third_party) emacsPackagesGen emacs27; + inherit (pkgs) writeShellScript writeShellScriptBin; + inherit (pkgs.lib.strings) concatStringsSep makeBinPath; + + emacsBinPath = makeBinPath (with pkgs; [ + ripgrep + bat + fd + fzf + pass + tokei + nmap + tldr + diskus + jq + pup + exa + gitAndTools.hub + kubectl + google-cloud-sdk + xsv + scrot + clipmenu + xorg.xset + direnv + nix + ]); + + emacsWithPackages = (emacsPackagesGen emacs27).emacsWithPackages; + + wpcarrosEmacs = emacsWithPackages (epkgs: + (with epkgs.elpaPackages; [ + exwm + ]) ++ + + (with epkgs.melpaPackages; [ + org-bullets + sly + notmuch + elm-mode + ts + vterm + base16-theme + password-store + clipmon # TODO: Prefer an Emacs client for clipmenud. + evil + evil-collection + evil-magit + evil-commentary + evil-surround + key-chord + add-node-modules-path # TODO: Assess whether or not I need this with Nix. + web-mode + rjsx-mode + tide + prettier-js + flycheck + diminish + doom-themes + telephone-line + which-key + all-the-icons + all-the-icons-ivy + ivy + ivy-pass + ivy-prescient + restclient + package-lint + parsec + magit-popup + direnv + alert + nix-mode + racer + rust-mode + rainbow-delimiters + racket-mode + lispyville + elisp-slime-nav + py-yapf + reason-mode + elixir-mode + go-mode + company + markdown-mode + refine + deferred + magit + request + pcre2el + helpful + exec-path-from-shell # TODO: Determine if Nix solves this problem. + yasnippet + projectile + deadgrep + counsel + counsel-projectile + engine-mode # TODO: Learn what this is. + eglot + dap-mode + lsp-ui + company-lsp + suggest + paradox + flymake-shellcheck + fish-mode + tuareg + haskell-mode + lsp-haskell + use-package + general + clojure-mode + cider + f + dash + company + counsel + flycheck + ])); + + vendorDir = path { + path = ./.emacs.d/vendor; + name = "emacs-vendor"; + }; + + # TODO: byte-compile these by packaging each as an Elisp library. + wpcDir = path { + path = ./.emacs.d/wpc; + name = "emacs-libs"; + }; + + wpcPackageEl = path { + path = ./.emacs.d/wpc/wpc-package.el; + name = "wpc-package.el"; + }; + + initEl = path { + path = ./.emacs.d/init.el; + name = "init.el"; + }; + + loadPath = concatStringsSep ":" [ + wpcDir + vendorDir + # TODO: Explain why the trailing ":" is needed. + "${wpcarrosEmacs.deps}/share/emacs/site-lisp:" + ]; + + withEmacsPath = { emacsBin, briefcasePath ? "$HOME/briefcase" }: + writeShellScriptBin "wpcarros-emacs" '' + export XMODIFIERS=emacs + export BRIEFCASE=${briefcasePath} + export GOOGLE_BRIEFCASE="$HOME/google-briefcase" + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${loadPath}" + exec ${emacsBin} \ + --debug-init \ + --no-init-file \ + --no-site-file \ + --no-site-lisp \ + --load ${initEl} \ + "$@" + ''; +in { + inherit initEl withEmacsPath; + + # I need to start my Emacs from CI without the call to `--load ${initEl}`. + runScript = { script, briefcasePath }: + writeShellScript "run-emacs-script" '' + export BRIEFCASE=${briefcasePath} + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${wpcDir}:${vendorDir}:${wpcarrosEmacs.deps}/share/emacs/site-lisp" + exec ${wpcarrosEmacs}/bin/emacs \ + --no-site-file \ + --no-site-lisp \ + --no-init-file \ + --script ${script} \ + "$@" + ''; + + # Use `nix-env -f '<briefcase>' emacs.nixos` to install `wpcarros-emacs` on + # NixOS machines. + nixos = { briefcasePath ? "$HOME/briefcase" }: withEmacsPath { + inherit briefcasePath; + emacsBin = "${wpcarrosEmacs}/bin/emacs"; + }; +} diff --git a/users/wpcarro/emacs/elisp-conventions.md b/users/wpcarro/emacs/elisp-conventions.md new file mode 100644 index 000000000000..0e39c3069d8b --- /dev/null +++ b/users/wpcarro/emacs/elisp-conventions.md @@ -0,0 +1,20 @@ +# Elisp Conventions + +Some of this aligns with existing style guides. Some of it does not. + +In general, prefer functions with fixed arities instead of variadic +alternatives. + +- Namespace functions with `namespace/function-name` +- Use `ensure`, `assert`, `refute` whenever possible. +- When talking about encoding and decoding, let's use the words "encoding" and + "decoding" rather than the myriad of other variants that appear like: + - `marshalling` and `unmarshalling` + - `parse` and `deparse`, `serialize`, `stringify` + - `unpickle` and `pickle` (Python) + - `from-string` and `to-string` + - TODO: Add more examples of these; there should be close to a dozen. +- Annotate assertions with `!` endings. +- Prefer the Scheme style of `predicate?` +- Variadic functions *should* encode this by appending * onto their + name. E.g. `maybe/nil?*` diff --git a/users/wpcarro/emacs/keybindings.md b/users/wpcarro/emacs/keybindings.md new file mode 100644 index 000000000000..96ba7c96459b --- /dev/null +++ b/users/wpcarro/emacs/keybindings.md @@ -0,0 +1,47 @@ +# Keybindings + +Since I'm using Emacs to manage most of my workflow, all of the keybindings +should be defined herein and -- in order to scale -- order must be imposed. This +can help avoid KBD collisions and improve my ability to remember each KBD. + +See `kbd.el` for the programmatic encoding of these principles. + +## Troubleshooting + +When in doubt, use Emacs's `read-key` and `read-event` to learn what signal +you're sending Emacs. + +### Super- + +- EXWM X11 windows are not processing `s-`. +- EXWM X11 windows are not processing `<M-ESC>`. + +### Super-Ctrl- + +I'm reserving `C-s-` for opening X11 applications. + +- `terminator`: `t` +- `google-chrome`: `c` + +## Emacs nouns + +Most of my keybindings should be organized according to their function, which in +turn should be related to the following Emacs nouns. + +- `workspace`: As defined by EXWM. +- `frame`: What non-Emacs users would call a "window". Currently my workflow + doesn't use or rely on Emacs frames. +- `window`: A vertical or horizontal split within an Emacs frame. +- `buffer`: Anything storing text in memory. + +## Prefixes and their meanings + +TODO: Have a system for leader-prefixed KBDs, chords, and prefixed chords. + +- `s-`: Switching between named workspaces. Right now, super is too overloaded + and would benefit from having more deliberate keybindings. +- `C-M-`: Window sizing +- `M-{h,j,k,l}`: Window traversing +- `M-{\,-}`: Window splitting +- `M-q`: Window deletion +- `<leader>-q`: Window deletion diff --git a/users/wpcarro/emacs/snippets.md b/users/wpcarro/emacs/snippets.md new file mode 100644 index 000000000000..2081b5617184 --- /dev/null +++ b/users/wpcarro/emacs/snippets.md @@ -0,0 +1,22 @@ +# Snippets + +Specifying snippets that I plan on defining for most of the programming +languages with which I work. I hope this will serve as a checklist of language +constructs I should support when adopting a new language. + +## Shared language features + +These are language features that should be available across most of the +languages that I'm hoping to support. + +- `ld`: anonymous functions (i.e. lambdas) +- `fn`: named function definition +- `var`: variable definition + +## Miscellaneous other language KBDs + +Some of this is related to language tool must-haves, which may need to be a +separate document. + +- `<leader>d`: Show documentation +- `<leader>x`: Evaluate expression (works mostly for LISPs) diff --git a/users/wpcarro/go/.envrc b/users/wpcarro/go/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/go/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/go/actors.go b/users/wpcarro/go/actors.go new file mode 100644 index 000000000000..1409db185e84 --- /dev/null +++ b/users/wpcarro/go/actors.go @@ -0,0 +1,45 @@ +package main + +import ( + "bufio" + "fmt" + "os" +) + +// Call function `f` in a go-routine, passing a reference to a newly created +// channel, `c`, as its only argument. Return a reference to `c` to the caller +// of `act`. When `f` halts, close the channel. +func act(f func(chan interface{})) chan interface{} { + c := make(chan interface{}) + + go func() { + defer close(c) + f(c) + }() + + return c +} + +func prompt(msg string) string { + reader := bufio.NewReader(os.Stdin) + fmt.Print(msg) + text, _ := reader.ReadString('\n') + // TODO: Trim trailing newline from the rhs of text. + return text +} + +func main() { + c := act(func(c chan interface{}) { + for { + x := <-c + fmt.Printf("[A] Received value: %v\n", x) + + } + }) + + for { + x := prompt("[B] Enter a value: ") + c <- x + } + os.Exit(0) +} diff --git a/users/wpcarro/go/atomic-counters.go b/users/wpcarro/go/atomic-counters.go new file mode 100644 index 000000000000..6cbcd2ee4eaf --- /dev/null +++ b/users/wpcarro/go/atomic-counters.go @@ -0,0 +1,26 @@ +// Attempting to apply some of the lessons I learned here: +// https://gobyexample.com/atomic-counters +package main + +import ( + "fmt" + "sync" + "sync/atomic" +) + +func main() { + var count uint64 + var wg sync.WaitGroup + + for i := 0; i < 50; i += 1 { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 1000; j += 1 { + atomic.AddUint64(&count, 1) + } + }() + } + wg.Wait() + fmt.Println("Count: ", count) +} diff --git a/users/wpcarro/go/channels.go b/users/wpcarro/go/channels.go new file mode 100644 index 000000000000..cba8abfc9621 --- /dev/null +++ b/users/wpcarro/go/channels.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "sync/atomic" +) + +type readMsg struct { + key int + sender chan int +} + +type writeMsg struct { + key int + value int + sender chan bool +} + +func main() { + fmt.Println("Hello, go.") + + var readOps uint64 + var writeOps uint64 + var wg sync.WaitGroup + + reads := make(chan readMsg) + writes := make(chan writeMsg) + + go func() { + state := make(map[int]int) + for { + select { + case msg := <-reads: + msg.sender <- state[msg.key] + case msg := <-writes: + state[msg.key] = msg.value + msg.sender <- true + } + } + }() + + // Reads + for i := 0; i < 100; i += 1 { + go func() { + wg.Add(1) + defer wg.Done() + for j := 0; j < 100; j += 1 { + msg := readMsg{ + key: rand.Intn(5), + sender: make(chan int)} + reads <- msg + val := <-msg.sender + fmt.Printf("Received %d.\n", val) + atomic.AddUint64(&readOps, 1) + } + }() + } + + // Writes + for i := 0; i < 100; i += 1 { + go func() { + wg.Add(1) + defer wg.Done() + for j := 0; j < 100; j += 1 { + msg := writeMsg{ + key: rand.Intn(5), + value: rand.Intn(10), + sender: make(chan bool)} + writes <- msg + <-msg.sender + fmt.Printf("Set %d as %d in state\n", msg.key, msg.value) + atomic.AddUint64(&writeOps, 1) + } + }() + } + + wg.Wait() + fmt.Printf("Read ops: %d\tWrite ops: %d\n", atomic.LoadUint64(&readOps), atomic.LoadUint64(&writeOps)) +} diff --git a/users/wpcarro/go/mutex.go b/users/wpcarro/go/mutex.go new file mode 100644 index 000000000000..5cea20754bed --- /dev/null +++ b/users/wpcarro/go/mutex.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "sync/atomic" + "time" +) + +func main() { + state := make(map[int]int) + mux := &sync.Mutex{} + + var readOps uint64 + var writeOps uint64 + + // Read from state + for i := 0; i < 1000; i += 1 { + for j := 0; j < 100; j += 1 { + go func() { + key := rand.Intn(5) + mux.Lock() + fmt.Printf("state[%d] = %d\n", key, state[key]) + mux.Unlock() + atomic.AddUint64(&readOps, 1) + time.Sleep(time.Millisecond) + }() + } + } + + // Write to state + for i := 0; i < 10; i += 1 { + for j := 0; j < 100; j += 1 { + go func() { + key := rand.Intn(5) + mux.Lock() + state[key] += 1 + mux.Unlock() + fmt.Printf("Wrote to state[%d].\n", key) + atomic.AddUint64(&writeOps, 1) + time.Sleep(time.Millisecond) + }() + } + } + + time.Sleep(time.Millisecond) + + mux.Lock() + fmt.Printf("State: %v\n", state) + mux.Unlock() + fmt.Printf("Reads: %d\tWrites: %d\n", atomic.LoadUint64(&readOps), atomic.LoadUint64(&writeOps)) +} diff --git a/users/wpcarro/go/shell.nix b/users/wpcarro/go/shell.nix new file mode 100644 index 000000000000..e14bffae487c --- /dev/null +++ b/users/wpcarro/go/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + go + goimports + godef + ]; +} diff --git a/users/wpcarro/go/waitgroups.go b/users/wpcarro/go/waitgroups.go new file mode 100644 index 000000000000..816321b8770f --- /dev/null +++ b/users/wpcarro/go/waitgroups.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "sync" + "time" +) + +func saySomething(x string, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Println(x) + time.Sleep(time.Second) + fmt.Printf("Finished saying \"%s\"\n", x) +} + +func main() { + var wg sync.WaitGroup + var things = [5]string{"chicken", "panini", "cheeseburger", "rice", "bread"} + for i := 0; i < 5; i += 1 { + wg.Add(1) + go saySomething(things[i], &wg) + } + wg.Wait() +} diff --git a/users/wpcarro/gopkgs/kv/default.nix b/users/wpcarro/gopkgs/kv/default.nix new file mode 100644 index 000000000000..c89e002139e2 --- /dev/null +++ b/users/wpcarro/gopkgs/kv/default.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.buildGo.package { + name = "kv"; + srcs = [ + ./kv.go + ]; +} diff --git a/users/wpcarro/gopkgs/kv/kv.go b/users/wpcarro/gopkgs/kv/kv.go new file mode 100644 index 000000000000..040cc63e0e7c --- /dev/null +++ b/users/wpcarro/gopkgs/kv/kv.go @@ -0,0 +1,39 @@ +// Supporting reading and writing key-value pairs to disk. +package kv + +import ( + "encoding/json" + "io/ioutil" + "log" + "path" +) + +// Return the decoded store from disk. +func getStore(storePath string) map[string]interface{} { + b, err := ioutil.ReadFile(path.Join(storePath, "kv.json")) + if err != nil { + log.Fatal("Could not read store: ", err) + } + var state map[string]interface{} + err = json.Unmarshal(b, &state) + if err != nil { + log.Fatal("Could not decode store as JSON: ", err) + } + return state +} + +// Set `key` to `value` in the store. +func Set(storePath string, key string, value interface{}) error { + state := getStore(storePath) + state[key] = value + b, err := json.Marshal(state) + if err != nil { + log.Fatal("Could not encode state as JSON: ", err) + } + return ioutil.WriteFile(path.Join(storePath, "kv.json"), b, 0644) +} + +// Get `key` from the store. +func Get(storePath string, key string) interface{} { + return getStore(path.Join(storePath, "kv.json"))[key] +} diff --git a/users/wpcarro/gopkgs/utils/default.nix b/users/wpcarro/gopkgs/utils/default.nix new file mode 100644 index 000000000000..5955fa4cfbde --- /dev/null +++ b/users/wpcarro/gopkgs/utils/default.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.buildGo.package { + name = "utils"; + srcs = [ + ./utils.go + ]; +} diff --git a/users/wpcarro/gopkgs/utils/utils.go b/users/wpcarro/gopkgs/utils/utils.go new file mode 100644 index 000000000000..7d662d08668b --- /dev/null +++ b/users/wpcarro/gopkgs/utils/utils.go @@ -0,0 +1,131 @@ +// Some utility functions to tidy up my Golang. +package utils + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "os" + "os/user" + "path/filepath" +) + +// Return the absolute path to the current uesr's home directory. +func HomeDir() string { + user, err := user.Current() + if err != nil { + log.Fatal(err) + } + return user.HomeDir +} + +// Returns true if `info` is a symlink. +func IsSymlink(info os.FileMode) bool { + return info&os.ModeSymlink != 0 +} + +// Return true if `path` exists and false otherwise. +func FileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } else { + return true + } +} + +// Return the absolute file path of `file` using the following resolution +// strategy: +// - Traverse and search upwards until you reach the user's home directory +// - Return the first path in `backupPaths` that exists +// - Fail +func Resolve(fileName string, backupPaths []string) string { + // TODO(wpcarro): Drop hardcoding when whoami behaves as expected. + boundary := "/home" + cwd := "." + files, _ := ioutil.ReadDir(cwd) + + for { + fullCwd, _ := filepath.Abs(cwd) + if fullCwd == boundary { + break + } + for _, file := range files { + if file.Name() == fileName { + path, _ := filepath.Abs(cwd + "/" + file.Name()) + return path + } + } + cwd += "/.." + files, _ = ioutil.ReadDir(cwd) + } + + // TODO(wpcarro): Support expanding these paths to allow the consumer to + // pass in relative paths, and paths with "~" in them. + for _, backup := range backupPaths { + if FileExists(backup) { + return backup + } + } + log.Fatal("Cannot find a run.json to use.") + // This code should be unreachable. + return "" +} + +// Call log.Fatal with `err` when it's not nil. +func FailOn(err error) { + if err != nil { + log.Fatal(err) + } +} + +// Prints the verbose form of an HTTP request. +func DebugRequest(req *http.Request) { + bytes, _ := httputil.DumpRequest(req, true) + fmt.Println(string(bytes)) +} + +// Prints out the verbose form of an HTTP response. +func DebugResponse(res *http.Response) { + bytes, _ := httputil.DumpResponse(res, true) + fmt.Println(string(bytes)) +} + +// Make a simple GET request to `url`. Fail if anything returns an error. I'd +// like to accumulate a library of these, so that I can write scrappy Go +// quickly. For now, this function just returns the body of the response back as +// a string. +func SimpleGet(url string, headers map[string]string, debug bool) string { + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatal(err) + } + for k, v := range headers { + req.Header.Add(k, v) + } + + res, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() + + if debug { + DebugRequest(req) + DebugResponse(res) + } + + if res.StatusCode == http.StatusOK { + bytes, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatal(err) + } + return string(bytes) + } else { + log.Println(res) + log.Fatalf("HTTP status code of response not OK: %v\n", res.StatusCode) + return "" + } +} diff --git a/users/wpcarro/haskell-file/.envrc b/users/wpcarro/haskell-file/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/haskell-file/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/haskell-file/README.md b/users/wpcarro/haskell-file/README.md new file mode 100644 index 000000000000..3f3ac1474b49 --- /dev/null +++ b/users/wpcarro/haskell-file/README.md @@ -0,0 +1,7 @@ +# haskell-file + +This is a half-baked project. I'd like to write a library whose API closely +resembles some of the more modern filesystem APIs to which I am accustomed: +notably f.el for Elisp. + +I expect more development to come. diff --git a/users/wpcarro/haskell-file/f-todo.org b/users/wpcarro/haskell-file/f-todo.org new file mode 100644 index 000000000000..6dd43a96291f --- /dev/null +++ b/users/wpcarro/haskell-file/f-todo.org @@ -0,0 +1,67 @@ +* Paths +** TODO f-join (&rest args) +** TODO f-split (path) +** TODO f-expand (path &optional dir) +** TODO f-filename (path) +** TODO f-dirname (path) +** TODO f-common-parent (paths) +** TODO f-ext (path) +** TODO f-no-ext (path) +** TODO f-swap-ext (path ext) +** TODO f-base (path) +** TODO f-relative (path &optional dir) +** TODO f-short (path) +** TODO f-long (path) +** TODO f-canonical (path) +** TODO f-slash (path) +** TODO f-full (path) +** TODO f-uniquify (paths) +** TODO f-uniquify-alist (paths) +* I/O +** TODO f-read-bytes (path) +** TODO f-write-bytes (data path) +** TODO f-read-text (path &optional coding) +** TODO f-write-text(text coding path) +** TODO f-append-text(text coding path) +** TODO f-append-bytes(text coding path) +** TODO Destructive +** TODO f-mkdir (&rest dirs) +** TODO f-delete (path &optional force) +** TODO f-symlink (source path) +** TODO f-move (from to) +** TODO f-copy (from to) +** TODO f-copy-contenst (from to) +** TODO f-touch (path) +** TODO Predicates +** TODO f-exists? (path) +** TODO f-directory? (path) +** TODO f-file? (path) +** TODO f-symlink? (path) +** TODO f-readable? (path) +** TODO f-writable? (path) +** TODO f-executable? (path) +** TODO f-absolute? (path) +** TODO f-relative? (path) +** TODO f-root? (path) +** TODO f-ext? (path ext) +** TODO f-same? (path-a path-b) +** TODO f-parent-of? (path-a path-b) +** TODO f-child-of? (path-a path-b) +** TODO f-ancestor-of? (path-a path-b) +** TODO f-descendant-of? (path-a path-b) +** TODO f-hidden? (path) +** TODO f-empty? (path) +** TODO Stats +** TODO f-size (path) +** f-depth (path) + +* Misc +** TODO f-this-file () +** TODO f-path-separator () +** TODO f-glob (pattern &optional path) +** TODO f-entries (path &optional fn recursive) +** TODO f-directories (path &optional fn recursive) +** TODO f-files (path &optional fn recursive) +** TODO f-root () +** TODO f-traverse-upwards (fn &optional path) +** TODO f-with-sandbox (path-or-paths &rest body) diff --git a/users/wpcarro/haskell-file/f.hs b/users/wpcarro/haskell-file/f.hs new file mode 100644 index 000000000000..295575f3f48d --- /dev/null +++ b/users/wpcarro/haskell-file/f.hs @@ -0,0 +1,64 @@ +module F + ( join + , split + ) where + +-------------------------------------------------------------------------------- +-- Dependencies +-------------------------------------------------------------------------------- + +import Data.List (span) +import System.FilePath (FilePath, pathSeparator) +import System.FilePath.Posix (FilePath) +import qualified System.FilePath.Posix as F + +-- TODO: Move this to a misc.hs, prelude.hs, operators.hs; somewhere. +(|>) :: a -> (a -> b) -> b +(|>) a f = f a +infixl 1 |> + +-- TODO: Move this to a test_utils.hs or elsewhere. +simpleAssert :: (Eq a) => a -> a -> () +simpleAssert x y = + if x == y then + () + else + error "Assertion error" + +-------------------------------------------------------------------------------- +-- Library +-------------------------------------------------------------------------------- + +join :: [FilePath] -> FilePath +join = F.joinPath + +-- | Split path and return list containing parts. +split :: FilePath -> [String] +split = splitJoin . span (/= pathSeparator) + where + splitJoin :: (String, String) -> [String] + splitJoin ([], []) = [] + splitJoin (a, []) = [a] + splitJoin (a, [_]) = [a] + splitJoin (a, _:b) = a : split b + +-------------------------------------------------------------------------------- +-- Tests +-------------------------------------------------------------------------------- + +expected :: [([FilePath], FilePath)] +expected = [ (["path"], "path") + , (["/path"], "/path") + , (["path", "to", "file"], "path/to/file") + , (["/path", "to", "file"], "/path/to/file") + , (["/"], "/") + ] + +runTests :: [()] +runTests = + fmap (\(input, expected) -> simpleAssert (join input) expected) expected + +main :: IO () +main = do + print runTests + pure () diff --git a/users/wpcarro/haskell-file/shell.nix b/users/wpcarro/haskell-file/shell.nix new file mode 100644 index 000000000000..4d5b412a0884 --- /dev/null +++ b/users/wpcarro/haskell-file/shell.nix @@ -0,0 +1,5 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: []; +} diff --git a/users/wpcarro/haskell-file/tests.hs b/users/wpcarro/haskell-file/tests.hs new file mode 100644 index 000000000000..e3967b77de1f --- /dev/null +++ b/users/wpcarro/haskell-file/tests.hs @@ -0,0 +1,39 @@ +module FTest where +-------------------------------------------------------------------------------- +import Test.Tasty +import Test.Tasty.Hedgehog +import Hedgehog +-------------------------------------------------------------------------------- +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +-------------------------------------------------------------------------------- +import Data.List (intercalate) +import System.FilePath (pathSeparator) +-------------------------------------------------------------------------------- +import F +-------------------------------------------------------------------------------- +main :: IO () +main + = defaultMain + . localOption (HedgehogTestLimit $ Just 50) + $ testGroup "f functions" + [ test_split + ] +-------------------------------------------------------------------------------- +test_split :: TestTree +test_split + = testGroup "split function" + [ testProperty "splits parts properly" splitSuccess + ] +splitSuccess :: Property +splitSuccess = property $ do + -- separator + -- <- H.forAll + -- $ Gen.element ['/', '\\'] + parts + <- H.forAll + . Gen.list (Range.linear 0 10) + $ Gen.list (Range.linear 1 10) Gen.alphaNum + let path = intercalate [pathSeparator] parts + F.split path === parts diff --git a/users/wpcarro/lisp/README.md b/users/wpcarro/lisp/README.md new file mode 100644 index 000000000000..9f8693fa6a10 --- /dev/null +++ b/users/wpcarro/lisp/README.md @@ -0,0 +1,16 @@ +# Common Lisp + +Things that I like about Common Lisp: +- It's an S-expression based language. +- It has a powerful macro system +- It has a unique way of handling-errors +- It is highly introspectible +- The tooling integration with Emacs is the best I have ever seen for any language + +Things that I don't like about Common Lisp: +- I find its standard libraries difficult to use and -- compared to modern + libraries -- like Golang's or Elixir's standard libraries, Common Lisp's + libraries are clunky + +As such, I would like to modernize CL's libraries to resemble other libraries +with which I am more familiar and, therefore, productive. diff --git a/users/wpcarro/lisp/f/README.md b/users/wpcarro/lisp/f/README.md new file mode 100644 index 000000000000..34e07180d492 --- /dev/null +++ b/users/wpcarro/lisp/f/README.md @@ -0,0 +1,5 @@ +# f.lisp + +In this project, I'm attempting to port the Elisp library [`f.el`][1] to Common Lisp. + +[1]: https://github.com/rejeep/f.el diff --git a/users/wpcarro/lisp/f/default.nix b/users/wpcarro/lisp/f/default.nix new file mode 100644 index 000000000000..f64bfcc5f0d1 --- /dev/null +++ b/users/wpcarro/lisp/f/default.nix @@ -0,0 +1,11 @@ +{ depot, briefcase, ... }: + +depot.nix.buildLisp.library { + name = "f"; + deps = with briefcase.lisp; [ + prelude + ]; + srcs = [ + ./main.lisp + ]; +} diff --git a/users/wpcarro/lisp/f/main.lisp b/users/wpcarro/lisp/f/main.lisp new file mode 100644 index 000000000000..a51c38127815 --- /dev/null +++ b/users/wpcarro/lisp/f/main.lisp @@ -0,0 +1,48 @@ +(in-package #:cl-user) +(defpackage #:main + (:documentation "Modern API for working with files and directories.") + (:use #:cl) + (:shadow #:type)) +(in-package #:main) + +;; Common Lisp distinguishes between `namestrings` and `pathnames` as two types +;; of filename representations. +;; +;; A `pathname` is a structured representation of the name of a file, which +;; consists of six parts: +;; 1. host +;; 2. device +;; 3. directory +;; 4. name +;; 5. type +;; 6. version + +;; TODO: Should I be using `string` as a type or `namestring`? + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(type join (&rest namestring) pathname) +(defun join (&rest args) + "Join ARGS to a single path." + (apply #'merge-pathnames args)) + +(type ext (pathname) string) +(defun ext (path) + "Return the file extension of PATH." + (pathname-type path)) + +;; TODO: Define these tests elsewhere. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; join +(string= (join "path") "path") +(string= (join "path" "to") "path/to") +(string= (join "/" "path" "to" "heaven") "/path/to/heaven") + +;; ext +(string= (ext #p"path/to/file.ext") "ext") +(string= (ext #p"path/to/directory") nil) diff --git a/users/wpcarro/lisp/prelude.lisp b/users/wpcarro/lisp/prelude.lisp new file mode 100644 index 000000000000..3522567ea0f7 --- /dev/null +++ b/users/wpcarro/lisp/prelude.lisp @@ -0,0 +1,14 @@ +(in-package #:cl-user) +(defpackage #:prelude + (:documentation "Supporting miscellaneous utility functions and macros.") + (:use #:cl) + (:shadow #:type) + (:export #:type #:comment)) +(in-package #:prelude) + +;; TODO: Add documentation to these macros. + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(defmacro comment (&rest _forms) nil) diff --git a/users/wpcarro/lisp/prelude.nix b/users/wpcarro/lisp/prelude.nix new file mode 100644 index 000000000000..5fe5d628e099 --- /dev/null +++ b/users/wpcarro/lisp/prelude.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.nix.buildLisp.library { + name = "prelude"; + srcs = [ + ./prelude.lisp + ]; +} diff --git a/users/wpcarro/nixos/installer.nix b/users/wpcarro/nixos/installer.nix new file mode 100644 index 000000000000..0dff8ea07d13 --- /dev/null +++ b/users/wpcarro/nixos/installer.nix @@ -0,0 +1,12 @@ +# This expression can be used to create NixOS .iso images. +{ ... }: + +{ + imports = [ + <nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix> + ]; + config = { + networking.wireless.enable = true; + networking.wireless.networks."GoogleGuest" = {}; + }; +} diff --git a/users/wpcarro/nixos/socrates/default.nix b/users/wpcarro/nixos/socrates/default.nix new file mode 100644 index 000000000000..8b762a56de5f --- /dev/null +++ b/users/wpcarro/nixos/socrates/default.nix @@ -0,0 +1,218 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in { + imports = [ ./hardware.nix ]; + + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + networking = { + hostName = "socrates"; + # The global useDHCP flag is deprecated, therefore explicitly set to false + # here. Per-interface useDHCP will be mandatory in the future, so this + # generated config replicates the default behaviour. + useDHCP = false; + networkmanager.enable = true; + interfaces.enp2s0f1.useDHCP = true; + interfaces.wlp3s0.useDHCP = true; + firewall.allowedTCPPorts = [ 9418 80 443 6697 ]; + }; + + time.timeZone = "UTC"; + + programs.fish.enable = true; + programs.mosh.enable = true; + + environment.systemPackages = with pkgs; [ + curl + direnv + emacs26-nox + gnupg + htop + pass + vim + certbot + tree + git + ]; + + users = { + # I need a git group to run the git server. + groups.git = {}; + + users.wpcarro = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + shell = pkgs.fish; + }; + + users.git = { + group = "git"; + isNormalUser = false; + }; + }; + + nix = { + nixPath = []; + trustedUsers = [ "root" "wpcarro" ]; + }; + + ############################################################################## + # Services + ############################################################################## + + systemd.services.bitlbee-stunnel = { + description = "Provides TLS termination for Bitlbee."; + wantedBy = [ "multi-user.target" ]; + unitConfig = { + Restart = "always"; + User = "nginx"; # This is a hack to easily get certificate access. + }; + script = let configFile = builtins.toFile "stunnel.conf" '' + foreground = yes + debug = 7 + + [ircs] + accept = 0.0.0.0:6697 + connect = 6667 + cert = /var/lib/acme/wpcarro.dev/full.pem + ''; in "${pkgs.stunnel}/bin/stunnel ${configFile}"; + }; + + nixpkgs.config.bitlbee.enableLibPurple = true; + services.bitlbee = { + interface = "0.0.0.0"; + enable = true; + libpurple_plugins = [ + pkgs.telegram-purple + ]; + }; + + services.journaldriver = { + enable = true; + logStream = "home"; + googleCloudProject = "wpcarros-infrastructure"; + applicationCredentials = "/etc/gcp/key.json"; + }; + + services.openssh.enable = true; + + services.gitea = { + enable = true; + # Without this the links to clone a repository like briefcase will be + # "http://localhost:3000/wpcarro/briefcase". + rootUrl = "https://git.wpcarro.dev/"; + }; + + services.buildkite-agents = { + socrates = { + enable = true; + tokenPath = "/etc/secrets/buildkite-agent-token"; + privateSshKeyPath = "/etc/ssh/buildkite_agent_id_rsa"; + }; + }; + + systemd.services.zoo = { + enable = true; + description = "Run my monoserver"; + script = "${briefcase.zoo}/zoo"; + environment = {}; + serviceConfig = { + Restart = "always"; + }; + }; + + services.gitDaemon = { + enable = true; + basePath = "/srv/git"; + exportAll = true; + repositories = [ "/srv/git/briefcase" ]; + }; + + # Since I'm using this laptop as a server in my flat, I'd prefer to close its + # lid. + services.logind.lidSwitch = "ignore"; + + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + polkit.log("subject.user: " + subject.user + " is attempting action.id: " + action.id); + }); + ''; + + # Provision SSL certificates to support HTTPS connections. + security.acme.acceptTerms = true; + security.acme.email = "wpcarro@gmail.com"; + + services.nginx = { + enable = true; + enableReload = true; + + recommendedTlsSettings = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + commonHttpConfig = '' + log_format json_combined escape=json + '{' + '"remote_addr":"$remote_addr",' + '"method":"$request_method",' + '"host":"$host",' + '"uri":"$request_uri",' + '"status":$status,' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ''; + + virtualHosts = { + "wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website; + }; + "learn.wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website.learn; + }; + "git.wpcarro.dev" = { + addSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:3000"; + }; + }; + "blog.wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website.blog; + }; + # "sandbox.wpcarro.dev" = { + # addSSL = true; + # enableACME = true; + # root = briefcase.website.sandbox; + # }; + # "learnpianochords.app" = { + # addSSL = true; + # enableACME = true; + # root = briefcase.website.sandbox.learnpianochords; + # }; + "zoo.wpcarro.dev" = { + addSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:8000"; + }; + }; + }; + }; + + system.stateVersion = "20.09"; +} diff --git a/users/wpcarro/nixos/socrates/hardware.nix b/users/wpcarro/nixos/socrates/hardware.nix new file mode 100644 index 000000000000..dde14eb1e627 --- /dev/null +++ b/users/wpcarro/nixos/socrates/hardware.nix @@ -0,0 +1,30 @@ +# Do not modify this file! It was generated by โnixos-generate-configโ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix> + ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/aadf1a77-1e98-4b5f-8e74-abf8e77bda34"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/1613-35B9"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 2; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; +} diff --git a/users/wpcarro/playbooks/README.md b/users/wpcarro/playbooks/README.md new file mode 100644 index 000000000000..70a26c8e8914 --- /dev/null +++ b/users/wpcarro/playbooks/README.md @@ -0,0 +1,3 @@ +# playbooks + +Here's the vision: playbooks for everything - not just software. diff --git a/users/wpcarro/playbooks/first-of-the-month.org b/users/wpcarro/playbooks/first-of-the-month.org new file mode 100644 index 000000000000..98d6d591f244 --- /dev/null +++ b/users/wpcarro/playbooks/first-of-the-month.org @@ -0,0 +1,13 @@ +# In total this should take one hour to complete. This is a substantial amount +# of time, which may disincentivize me from completing it. This time is +# amortized over the length of its usefulness (i.e. an entire month), so it +# should be thought of instead as two-minutes worth of work per day that is all +# being completed upfront. +* Tasks +** TODO [20m] Create habit template in journal. +*** Spend time choosing a habit that you can accomplish giving known traveling constraints. +** TODO [45m] Assess previous month's performance. +** TODO [10m] Book massage for the month. +** TODO [05m] Register for HotPodYoga classes. +** TODO [10m] Plan one museum date in London. +** TODO [20m] Plan each weekend for month. diff --git a/users/wpcarro/playbooks/habits.org b/users/wpcarro/playbooks/habits.org new file mode 100644 index 000000000000..3b6f6f680e4d --- /dev/null +++ b/users/wpcarro/playbooks/habits.org @@ -0,0 +1,54 @@ +* First of the year +** [1hr] Write a post mortem for the previous year +* First of the month +** [20m] Create habit template in journal. +** [45m] Assess previous month's performance. +** [10m] Book massage for the month. +** [05m] Register for HotPodYoga classes. +** [10m] Plan one museum date in London. +** [20m] Plan each weekend for month. +* Payday +** [10m] Audit Monzo expenses +** [05m] Review "finances_2020" spreadsheet +** [05m] Transfer GBP to USD account +** [10m] Withdraw cash from ATM +* Morning +** [00m] Wake up at 7:00 +** [15m] Read +** [02m] Brush teeth +** [01m] Make bed +** [01m] Water plants +** [10m] 12 rounds of forward folds +** [05m] 12 rounds Pranayama +** [30m] Transcendental meditation +** [10m] Shower +** [05m] Put on clothes +* Evening +** [01m] Layout tomorrow's outfit +** [01m] Floss +** [02m] Brush teeth +** [01m] Mouth wash +** [30m] Read +** [01m] Journal daily progress +* Monday +** [1hr] Jiu Jitsu +* Tuesday +** Work from 6PS +** [1hr] Jiu Jitsu +* Wednesday +** [1hr] Hot Yoga +** [10m] Shave +** [15m] Clean apartment sinks +* Thursday +* Friday +** [1hr] Hot Yoga +* Saturday +** [10m] Vacuum +** [30m] Nap +* Sunday +** [1hr] Jiu Jitsu +** [30m] Nap +** [10m] Shave +** [05m] Trim nails +** [05m] Take out trash +** [05m] Laundry diff --git a/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf b/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf new file mode 100644 index 000000000000..d292ef832c23 --- /dev/null +++ b/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf Binary files differdiff --git a/users/wpcarro/playbooks/hip_opening_challenge/progress.org b/users/wpcarro/playbooks/hip_opening_challenge/progress.org new file mode 100644 index 000000000000..80749a3c6b81 --- /dev/null +++ b/users/wpcarro/playbooks/hip_opening_challenge/progress.org @@ -0,0 +1,65 @@ +# From Lucas Rockwood's 21-day hip challenge from yogabody.com +* DONE day 1 +** pigeon +** butterfly +* DONE day 2 +** blaster +** squat +* DONE day 3 +** happy baby +** thread the needle (supine) +* DONE day 4 +** frog +** jackknife blaster +* DONE day 5 +** lightning bolt +** scissors +* DONE day 6 +** zorro +** supine butterfly (w/ strap) +* TODO day 7 +** thread the needle (wall) +** prone butterfly +* TODO day 8 +** ninja squat +** chair scissors +** lateral chain stretch +* TODO day 9 +** psoas blaster (chair) +** reclined scissors +* DONE day 10 +** twisted blaster +** twisted squat +* TODO day 11 +** double pigeon +** bound butterfly +* TODO day 12 +** eagle fold +** cross-thread +* DONE day 13 +** swiss army knife +** saddle +* TODO day 14 +** butterfly squat +** half lightning bolt +* DONE day 15 +** fallen blaster +** asymmetric baby +* DONE day 16 +** standing psoas +** standing pigeon +* TODO day 17 +** marichi B +** long butterfly +* TODO day 18 +** eagle legs +** chair squat +* DONE day 19 +** twisted pigeon +** bound baby +* DONE day 20 +** seated pigeon +** railroad squat +* DONE day 21 +** thunderbolt +** yogi squat diff --git a/users/wpcarro/playbooks/nix_gcr/README.md b/users/wpcarro/playbooks/nix_gcr/README.md new file mode 100644 index 000000000000..9d111cf6bba5 --- /dev/null +++ b/users/wpcarro/playbooks/nix_gcr/README.md @@ -0,0 +1,62 @@ +# Nix + Google Cloud Run (i.e. GCR) + +I'm documenting how I currently deploy projects that I package with Nix on +Google Cloud Run. + +I'd like to automate this workflow as much as possible, and I intend to do just +that. For now, I'm running things manually until I can design an generalization +that appeals to me. + +## Dependencies +- `nix-build` +- `docker` +- `gcloud` + +## Step-by-step + +1. Use `nix-build` to create our Docker image for Cloud Run. + +```shell +> nix-build ./cloud_run.nix +``` + +This outputs a Docker image at `./result`. + +1. Load the built image (i.e. `./result`) into `docker` so that we can tag it + and push it to the Google Container Registry (i.e. GCR). + +```shell +> sudo docker load <./result +``` + +1. (Optionally) Run the image locally to verify its integrity. + +```shell +> sudo docker run -d -p 8080:4242 <name>:<tag> +``` + +1. Tag and push the image to GCR. + +```shell +> sudo docker tag <name>:<label> gcr.io/<google-cloud-project-id>/<name>:<latest> +``` + +1. Visit Google Cloud Run; create a new service with "Create Service"; select + the uploaded Docker image from the "Container Image URL" field; click + "Create" to deploy. + +## Notes + +You may need to authorize `gcloud` by running the following: + +```shell +> sudo gcloud auth login --no-launch-browser +``` + +You must use `sudo` here since the `docker` invocations are prefixed with `sudo` +as well. + +## Todos + +- If possible, prefer using a command line tool like `gcloud` to create the + Cloud Run service. diff --git a/users/wpcarro/playbooks/nix_gcr/cloud_run.nix b/users/wpcarro/playbooks/nix_gcr/cloud_run.nix new file mode 100644 index 000000000000..3d981611817b --- /dev/null +++ b/users/wpcarro/playbooks/nix_gcr/cloud_run.nix @@ -0,0 +1,14 @@ +{ pkgs, depot, ... }: + +pkgs.dockerTools.buildLayeredImage { + name = "gemma"; + tag = "latest"; + config.ExposedPorts = { + "4242" = {}; + }; + config.Env = [ + "GEMMA_CONFIG=${./config.lisp}" + ]; + config.Cmd = [ "${depot.fun.gemma}/bin/gemma" ]; + maxLayers = 120; +} diff --git a/users/wpcarro/playbooks/nix_gcr/config.lisp b/users/wpcarro/playbooks/nix_gcr/config.lisp new file mode 100644 index 000000000000..54f8e5f34462 --- /dev/null +++ b/users/wpcarro/playbooks/nix_gcr/config.lisp @@ -0,0 +1,21 @@ +;; Example configuration file for Gemma + +(config :port 4242 + :data-dir "/tmp/gemma/") + +(deftask bathroom/wipe-mirror 7) +(deftask bathroom/wipe-counter 7) + +;; Bedroom tasks +(deftask bedroom/change-sheets 7) +(deftask bedroom/vacuum 10) + +;; Kitchen tasks +(deftask kitchen/normal-trash 3) +(deftask kitchen/green-trash 5) +(deftask kitchen/blue-trash 5) +(deftask kitchen/wipe-counters 3) +(deftask kitchen/vacuum 5 "Kitchen has more crumbs and such!") + +;; Entire place +(deftask clean-windows 60) diff --git a/users/wpcarro/playbooks/shell.md b/users/wpcarro/playbooks/shell.md new file mode 100644 index 000000000000..5eda417f489c --- /dev/null +++ b/users/wpcarro/playbooks/shell.md @@ -0,0 +1,12 @@ +# Shell + +I'm making this as an offline reference for some of the commands that I use +often enough to need to remember but not often enough to *actually* remember. + +## Reference + +- To kill a process by its port number: + +```shell +$ fuser 8080/tcp +``` diff --git a/users/wpcarro/playbooks/sqlite3.md b/users/wpcarro/playbooks/sqlite3.md new file mode 100644 index 000000000000..aec87f0b59ee --- /dev/null +++ b/users/wpcarro/playbooks/sqlite3.md @@ -0,0 +1,115 @@ +# SQLite3 + +Creating a reference for SQLite that I can access when I'm offline +(e.g. traveling in an airplane). + +## Benefits + +I enjoy using SQLite because it's lightweight and simple. Instead of networking +microservices, I can oftentimes just create a simple `db.sqlite3` file and get +significant mileage without much tooling overhead. + +## Limitations + +SQLite has some limitations; here are some of the limitations that I have encountered. + +- SQLite **disables** support for `FOREIGN KEY` by default. Enable it with: + +``` +sqlite> PRAGMA foreign_keys = ON; +``` + +- SQLite has no `BOOLEAN` type; it uses 0 and 1 instead. + +``` +sqlite> SELECT TRUE; +TRUE +---------- +1 +sqlite> SELECT FALSE; +FALSE +---------- +0 +``` + +- SQLite has no `DATETIME` type; it uses `TEXT` instead. + +``` +sqlite> SELECT datetime('now'); +datetime('now') +------------------- +2020-07-26 09:52:32 +``` + +## Reference + +The following should serve as a useful reference for working with SQLite. + +### Schema + +```sql +CREATE TABLE IF NOT EXISTS Movies ( + title TEXT NOT NULL, + year INTEGER, + PRIMARY KEY (title) +); + +ALTER TABLE Movies ADD COLUMN rating DEFAULT 0.0; + +DROP TABLE Movies; +``` + +### Queries + +The following queries should come in handy as a reference: + +``` +sqlite> -- I'm using an intentionally incorrect date here for the subsequent UPDATE. +sqlite> INSERT INTO Movies (title, year) VALUES ('Toy Story 3', 2100); +sqlite> SELECT * FROM Movies WHERE year IS NULL; +sqlite> UPDATE Movies SET year = 2010 WHERE title = 'Toy Story 3'; +sqlite> -- % is like .* in a regex +sqlite> DELETE FROM Movies WHERE title LIKE 'Toy Story%'; +``` + +## Command Line + +- Create a `~/.sqliterc` file with the following contents: + +``` +.mode column +.headers on +``` + +- To start an interactive session: + +```shell +$ sqlite3 db.sqlite3 +``` + +- To create a SQLite database from a `.sql` file: + +```shell +$ sqlite3 db.sqlite3 <db.sql +``` + +- To reload changes to a `.sql` file while in an interactive session: + +``` +sqlite> .read db.sql +``` + +## Miscellaneous + +- For a web-browser-based SQLite viewer, run the following: + +```shell +$ sqlite_web db.sqlite3 +``` + +- To import a CSV: + +``` +sqlite> .mode csv <table-name> +sqlite> .import path/to/file.csv <table-name> +``` diff --git a/users/wpcarro/scratch/README.md b/users/wpcarro/scratch/README.md new file mode 100644 index 000000000000..8259ac70d9b2 --- /dev/null +++ b/users/wpcarro/scratch/README.md @@ -0,0 +1,6 @@ +# Scratch + +The purpose of the `scratch` directory is to host practice exercises. Practice +encompasses things like working on data structures and algorithms problems for +upcoming coding interviews or general aptitude as well as writing code snippets +to help me learn a new programming language or understand an unfamiliar concept. diff --git a/users/wpcarro/scratch/advent-of-code-2019/README.md b/users/wpcarro/scratch/advent-of-code-2019/README.md new file mode 100644 index 000000000000..e7c105a7f60f --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/README.md @@ -0,0 +1,4 @@ +# 2019 Advent of Code + +Here are my attempts at the 2019 Advent of Code challenge before my dedication +to the effort plummeted. diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_1.py b/users/wpcarro/scratch/advent-of-code-2019/day_1.py new file mode 100644 index 000000000000..bd4024e3ec7d --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_1.py @@ -0,0 +1,119 @@ +from math import floor + +xs = [ + 102473, + 84495, + 98490, + 68860, + 62204, + 72810, + 65185, + 145951, + 77892, + 108861, + 70764, + 67286, + 74002, + 80773, + 52442, + 131505, + 107162, + 126993, + 59784, + 64231, + 91564, + 68585, + 98735, + 69020, + 77332, + 60445, + 65826, + 111506, + 95431, + 146687, + 135119, + 86804, + 95915, + 85434, + 111303, + 148127, + 132921, + 136213, + 89004, + 143137, + 144853, + 143017, + 104386, + 100612, + 54760, + 63813, + 144191, + 84481, + 69718, + 84936, + 98621, + 124993, + 92736, + 60369, + 137284, + 101902, + 112726, + 51784, + 126496, + 85005, + 101661, + 137278, + 136637, + 90340, + 100209, + 53683, + 50222, + 132060, + 98797, + 139054, + 135638, + 100632, + 137849, + 125333, + 103981, + 76954, + 134352, + 74229, + 93402, + 62552, + 50286, + 57066, + 98439, + 120708, + 117827, + 107884, + 72837, + 148663, + 125645, + 61460, + 120555, + 142473, + 106668, + 58612, + 58576, + 143366, + 90058, + 121087, + 89546, + 126161, +] + + +def fuel_for_mass(x): + """Return the amount of fuel (in mass) required for a mass of X. The total + amount of fuel includes the amount of fuel required for the fuel itself, + since fuel also has a mass weights.""" + mass_fuel = floor(x / 3) - 2 + if mass_fuel < 0: + return 0 + else: + fuel_fuel = fuel_for_mass(mass_fuel) + return mass_fuel + fuel_fuel + + +print(sum(fuel_for_mass(x) for x in xs)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_2.py b/users/wpcarro/scratch/advent-of-code-2019/day_2.py new file mode 100644 index 000000000000..77774c1bb5ad --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_2.py @@ -0,0 +1,32 @@ +from itertools import product + +x = [ + 1, 0, 0, 3, 1, 1, 2, 3, 1, 3, 4, 3, 1, 5, 0, 3, 2, 1, 10, 19, 1, 6, 19, 23, + 2, 23, 6, 27, 2, 6, 27, 31, 2, 13, 31, 35, 1, 10, 35, 39, 2, 39, 13, 43, 1, + 43, 13, 47, 1, 6, 47, 51, 1, 10, 51, 55, 2, 55, 6, 59, 1, 5, 59, 63, 2, 9, + 63, 67, 1, 6, 67, 71, 2, 9, 71, 75, 1, 6, 75, 79, 2, 79, 13, 83, 1, 83, 10, + 87, 1, 13, 87, 91, 1, 91, 10, 95, 2, 9, 95, 99, 1, 5, 99, 103, 2, 10, 103, + 107, 1, 107, 2, 111, 1, 111, 5, 0, 99, 2, 14, 0, 0 +] + + +def interpret(i, x): + op, a, b, out = x[i + 0], x[i + 1], x[i + 2], x[i + 3] + if op == 1: + x[out] = x[a] + x[b] + return interpret(i + 4, x) + elif op == 2: + x[out] = x[a] * x[b] + return interpret(i + 4, x) + elif op == 99: + return x + else: + raise Exception('Unsupported opcode: {}. {}, {}'.format(op, a, b)) + + +for a, b in product(range(100), range(100)): + y = x[:] + y[1] = a + y[2] = b + if interpret(0, y)[0] == 19690720: + print(100 * a + b) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_3.py b/users/wpcarro/scratch/advent-of-code-2019/day_3.py new file mode 100644 index 000000000000..6dd863528c1c --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_3.py @@ -0,0 +1,137 @@ +from math import floor +from heapq import heappush, heappop + +xs = [ + "R1009", "U993", "L383", "D725", "R163", "D312", "R339", "U650", "R558", + "U384", "R329", "D61", "L172", "D555", "R160", "D972", "L550", "D801", + "L965", "U818", "L123", "D530", "R176", "D353", "L25", "U694", "L339", + "U600", "L681", "D37", "R149", "D742", "R762", "U869", "R826", "U300", + "L949", "U978", "L303", "U361", "R136", "D343", "L909", "U551", "R745", + "U913", "L566", "D292", "R820", "U886", "R205", "D431", "L93", "D71", + "R577", "U872", "L705", "U510", "L698", "U963", "R607", "U527", "L669", + "D543", "R690", "U954", "L929", "D218", "R490", "U500", "L589", "D332", + "R949", "D538", "R696", "U659", "L188", "U468", "L939", "U833", "L445", + "D430", "R78", "D303", "R130", "D649", "R849", "D712", "L511", "U745", + "R51", "U973", "R799", "U829", "R605", "D771", "L837", "U204", "L414", + "D427", "R538", "U116", "R540", "D168", "R493", "U900", "L679", "U431", + "L521", "D500", "L428", "U332", "L954", "U717", "L853", "D339", "L88", + "U807", "L607", "D496", "L163", "U468", "L25", "U267", "L759", "D898", + "L591", "U445", "L469", "U531", "R596", "D486", "L728", "D677", "R350", + "D429", "R39", "U568", "R92", "D875", "L835", "D841", "R877", "U178", + "L221", "U88", "R592", "U692", "R455", "U693", "L419", "U90", "R609", + "U672", "L293", "U168", "R175", "D456", "R319", "D570", "R504", "D165", + "L232", "D624", "L604", "D68", "R807", "D59", "R320", "D281", "L371", + "U956", "L788", "D897", "L231", "D829", "R287", "D798", "L443", "U194", + "R513", "D925", "L232", "U225", "L919", "U563", "R448", "D889", "R661", + "U852", "L950", "D558", "L269", "U186", "L625", "U673", "L995", "U732", + "R435", "U849", "L413", "D690", "L158", "D234", "R361", "D458", "L271", + "U90", "L781", "U754", "R256", "U162", "L842", "U927", "L144", "D62", + "R928", "D238", "R473", "U97", "L745", "U303", "L487", "D349", "L520", + "D31", "L825", "U385", "L133", "D948", "L39", "U62", "R801", "D664", + "L333", "U134", "R692", "U385", "L658", "U202", "L279", "D374", "R489", + "D686", "L182", "U222", "R733", "U177", "R94", "D603", "L376", "U901", + "R216", "D851", "L155", "D214", "L460", "U758", "R121", "D746", "L180", + "U175", "L943", "U146", "L166", "D251", "L238", "U168", "L642", "D341", + "R281", "U182", "R539", "D416", "R553", "D67", "L748", "U272", "R257", + "D869", "L340", "U180", "R791", "U138", "L755", "D976", "R731", "U713", + "R602", "D284", "L258", "U176", "R509", "U46", "R935", "U576", "R96", + "U89", "L913", "U703", "R833" +] +ys = [ + "L1006", "D998", "R94", "D841", "R911", "D381", "R532", "U836", "L299", + "U237", "R781", "D597", "L399", "D800", "L775", "D405", "L485", "U636", + "R589", "D942", "L878", "D779", "L751", "U711", "L973", "U410", "L151", + "U15", "L685", "U417", "L106", "D648", "L105", "D461", "R448", "D743", + "L589", "D430", "R883", "U37", "R155", "U350", "L421", "U23", "R337", + "U816", "R384", "D671", "R615", "D410", "L910", "U914", "L579", "U385", + "R916", "U13", "R268", "D519", "R289", "U410", "L389", "D885", "L894", + "U734", "L474", "U707", "L72", "U155", "L237", "U760", "L127", "U806", + "L15", "U381", "L557", "D727", "L569", "U320", "L985", "D452", "L8", + "D884", "R356", "U732", "L672", "D458", "L485", "U402", "L238", "D30", + "R644", "U125", "R753", "U183", "L773", "U487", "R849", "U210", "L164", + "D808", "L595", "D668", "L340", "U785", "R313", "D72", "L76", "D263", + "R689", "U604", "R471", "U688", "R462", "D915", "R106", "D335", "R869", + "U499", "R190", "D916", "R468", "D882", "R56", "D858", "L143", "D741", + "L386", "U856", "R50", "U853", "R151", "D114", "L773", "U854", "L290", + "D344", "L23", "U796", "L531", "D932", "R314", "U960", "R643", "D303", + "L661", "D493", "L82", "D491", "L722", "U848", "L686", "U4", "L985", + "D509", "L135", "D452", "R500", "U105", "L326", "D101", "R222", "D944", + "L645", "D362", "L628", "U305", "L965", "U356", "L358", "D137", "R787", + "U728", "R967", "U404", "R18", "D928", "L695", "D965", "R281", "D597", + "L791", "U731", "R746", "U163", "L780", "U41", "L255", "U81", "L530", + "D964", "R921", "D297", "R475", "U663", "L226", "U623", "L984", "U943", + "L143", "U201", "R926", "U572", "R343", "U839", "R764", "U751", "R128", + "U939", "R987", "D108", "R474", "U599", "R412", "D248", "R125", "U797", + "L91", "D761", "L840", "U290", "L281", "U779", "R650", "D797", "R185", + "D320", "L25", "U378", "L696", "U332", "R75", "D620", "L213", "D667", + "R558", "U267", "L846", "U306", "R939", "D220", "R311", "U827", "R345", + "U534", "R56", "D679", "R48", "D845", "R898", "U8", "R862", "D960", "R753", + "U319", "L886", "D795", "R805", "D265", "R876", "U729", "R894", "D368", + "R858", "U744", "R506", "D327", "L903", "U919", "L721", "U507", "L463", + "U753", "R775", "D719", "R315", "U128", "R17", "D376", "R999", "D386", + "L259", "U181", "L162", "U605", "L265", "D430", "R35", "D968", "R207", + "U466", "R796", "D667", "R93", "U749", "L315", "D410", "R312", "U929", + "L923", "U260", "R638" +] + + +def to_coords(xs): + row, col = 0, 0 + coords = [] + for x in xs: + d, amt = x[0], int(x[1:]) + if d == 'U': + for i in range(1, amt + 1): + coords.append((row + i, col)) + row += amt + elif d == 'D': + for i in range(1, amt + 1): + coords.append((row - i, col)) + row -= amt + elif d == 'L': + for i in range(1, amt + 1): + coords.append((row, col - i)) + col -= amt + elif d == 'R': + for i in range(1, amt + 1): + coords.append((row, col + i)) + col += i + return coords + + +def contains(row, col, d): + if row not in d: + return False + return col in d[row] + + +def intersections(xs, ys): + d = {} + ints = set() + for row, col in to_coords(xs): + if row in d: + d[row].add(col) + else: + d[row] = {col} + for row, col in to_coords(ys): + if contains(row, col, d): + ints.add((row, col)) + return ints + + +def trace_to(coord, xs): + count = 0 + for coord_x in to_coords(xs): + count += 1 + if coord_x == coord: + return count + raise Exception("Intersection doesn't exist") + + +answer = [] +for coord in intersections(xs, ys): + x = trace_to(coord, xs) + y = trace_to(coord, ys) + heappush(answer, x + y) + +print(heappop(answer)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_4.py b/users/wpcarro/scratch/advent-of-code-2019/day_4.py new file mode 100644 index 000000000000..adef73b452dc --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_4.py @@ -0,0 +1,35 @@ +import re + +start = 134792 +end = 675810 + + +def satisfies(x): + x = str(x) + result = False + double, not_decreasing = False, False + + # double and *only* double exists + for i in range(len(x) - 1): + # double and left-of-a is BOL or !x + # and right-of-b is EOL or !x + a, b = x[i], x[i + 1] + bol = i - 1 < 0 + eol = i + 2 >= len(x) + if a == b and (bol or x[i - 1] != a) and (eol or x[i + 2] != a): + double = True + break + + # not_decreasing + prev = int(x[0]) + for a in x[1:]: + a = int(a) + if prev > a: + return False + prev = a + not_decreasing = True + + return double and not_decreasing + + +print(len([x for x in range(start, end + 1) if satisfies(x)])) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_5.py b/users/wpcarro/scratch/advent-of-code-2019/day_5.py new file mode 100644 index 000000000000..3d82846e6126 --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_5.py @@ -0,0 +1,170 @@ +x = [ + 3, 225, 1, 225, 6, 6, 1100, 1, 238, 225, 104, 0, 1102, 31, 68, 225, 1001, + 13, 87, 224, 1001, 224, -118, 224, 4, 224, 102, 8, 223, 223, 1001, 224, 7, + 224, 1, 223, 224, 223, 1, 174, 110, 224, 1001, 224, -46, 224, 4, 224, 102, + 8, 223, 223, 101, 2, 224, 224, 1, 223, 224, 223, 1101, 13, 60, 224, 101, + -73, 224, 224, 4, 224, 102, 8, 223, 223, 101, 6, 224, 224, 1, 224, 223, + 223, 1101, 87, 72, 225, 101, 47, 84, 224, 101, -119, 224, 224, 4, 224, + 1002, 223, 8, 223, 1001, 224, 6, 224, 1, 223, 224, 223, 1101, 76, 31, 225, + 1102, 60, 43, 225, 1102, 45, 31, 225, 1102, 63, 9, 225, 2, 170, 122, 224, + 1001, 224, -486, 224, 4, 224, 102, 8, 223, 223, 101, 2, 224, 224, 1, 223, + 224, 223, 1102, 29, 17, 224, 101, -493, 224, 224, 4, 224, 102, 8, 223, 223, + 101, 1, 224, 224, 1, 223, 224, 223, 1102, 52, 54, 225, 1102, 27, 15, 225, + 102, 26, 113, 224, 1001, 224, -1560, 224, 4, 224, 102, 8, 223, 223, 101, 7, + 224, 224, 1, 223, 224, 223, 1002, 117, 81, 224, 101, -3645, 224, 224, 4, + 224, 1002, 223, 8, 223, 101, 6, 224, 224, 1, 223, 224, 223, 4, 223, 99, 0, + 0, 0, 677, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1105, 0, 99999, 1105, 227, 247, + 1105, 1, 99999, 1005, 227, 99999, 1005, 0, 256, 1105, 1, 99999, 1106, 227, + 99999, 1106, 0, 265, 1105, 1, 99999, 1006, 0, 99999, 1006, 227, 274, 1105, + 1, 99999, 1105, 1, 280, 1105, 1, 99999, 1, 225, 225, 225, 1101, 294, 0, 0, + 105, 1, 0, 1105, 1, 99999, 1106, 0, 300, 1105, 1, 99999, 1, 225, 225, 225, + 1101, 314, 0, 0, 106, 0, 0, 1105, 1, 99999, 8, 226, 677, 224, 102, 2, 223, + 223, 1005, 224, 329, 1001, 223, 1, 223, 1108, 677, 226, 224, 102, 2, 223, + 223, 1006, 224, 344, 101, 1, 223, 223, 108, 677, 226, 224, 102, 2, 223, + 223, 1006, 224, 359, 101, 1, 223, 223, 7, 677, 226, 224, 102, 2, 223, 223, + 1005, 224, 374, 101, 1, 223, 223, 1007, 226, 677, 224, 102, 2, 223, 223, + 1005, 224, 389, 101, 1, 223, 223, 8, 677, 677, 224, 102, 2, 223, 223, 1006, + 224, 404, 1001, 223, 1, 223, 1007, 677, 677, 224, 1002, 223, 2, 223, 1006, + 224, 419, 101, 1, 223, 223, 1108, 677, 677, 224, 1002, 223, 2, 223, 1005, + 224, 434, 1001, 223, 1, 223, 1107, 226, 677, 224, 102, 2, 223, 223, 1005, + 224, 449, 101, 1, 223, 223, 107, 226, 226, 224, 102, 2, 223, 223, 1006, + 224, 464, 101, 1, 223, 223, 1108, 226, 677, 224, 1002, 223, 2, 223, 1005, + 224, 479, 1001, 223, 1, 223, 7, 677, 677, 224, 102, 2, 223, 223, 1006, 224, + 494, 1001, 223, 1, 223, 1107, 677, 226, 224, 102, 2, 223, 223, 1005, 224, + 509, 101, 1, 223, 223, 107, 677, 677, 224, 1002, 223, 2, 223, 1006, 224, + 524, 101, 1, 223, 223, 1008, 677, 677, 224, 1002, 223, 2, 223, 1006, 224, + 539, 101, 1, 223, 223, 7, 226, 677, 224, 1002, 223, 2, 223, 1005, 224, 554, + 101, 1, 223, 223, 108, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 569, + 101, 1, 223, 223, 1008, 226, 677, 224, 102, 2, 223, 223, 1005, 224, 584, + 101, 1, 223, 223, 8, 677, 226, 224, 1002, 223, 2, 223, 1005, 224, 599, 101, + 1, 223, 223, 1007, 226, 226, 224, 1002, 223, 2, 223, 1005, 224, 614, 101, + 1, 223, 223, 1107, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 629, 101, + 1, 223, 223, 107, 677, 226, 224, 1002, 223, 2, 223, 1005, 224, 644, 1001, + 223, 1, 223, 1008, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 659, 101, + 1, 223, 223, 108, 677, 677, 224, 1002, 223, 2, 223, 1005, 224, 674, 1001, + 223, 1, 223, 4, 223, 99, 226 +] + +# Interpretter spec: +# Op-code width: 2 +# ABCDE +# A: Mode of 3rd parameter +# B: Mode of 2rd parameter +# C: Mode of 1st parameter +# DE: 2-digit op-code +# +# Not every op-code has the same arity. +# +# Parameter modes: +# - positional: index of memory. 0 +# - immediate: raw value. 1 +# Assert that you never attempt to write to an "immediate value" + +# Parameter modes +POS = '0' # positional parameter mode +VAL = '1' # immediate parameter mode + + +# Pasted from day-2.py +# interpretter :: Int -> [Int] -> [Int] -> IO () +def interpret(i, x, argv=[], outs=[]): + """Values in `argv` will be applied to any `input` fields.""" + # The widest op-code we'll see is 3 + 2 = 5 for either addition or + # multiplication since each of those is a 3-arity function with a two-digit + # op-code. + instruction = '{:05d}'.format(x[i]) + op = instruction[-2:] + + if op == '01': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + x[out] = a + b + return interpret(i + 4, x, argv=argv, outs=outs) + elif op == '02': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + x[out] = a * b + return interpret(i + 4, x, argv=argv, outs=outs) + # input + elif op == '03': + a = x[i + 1] + mode_a = instruction[2] + assert mode_a == POS + # What's the pythonic way to defensively get this value? + if len(argv) and argv[0] is not None: + x[a] = argv[0] + return interpret(i + 2, x, argv=argv[1:], outs=outs) + elif len(outs) and outs[-1] is not None: + x[a] = outs[-1] + return interpret(i + 2, x, argv=argv, outs=outs) + else: + # Here we want to block until the user applies input. This could be + # done easily with message passing for something similar. + x[a] = int(input('Enter: ')) + return interpret(i + 2, x, argv=argv) + # output + elif op == '04': + a = x[i + 1] + mode_a = instruction[2] + a = a if mode_a == VAL else x[a] + outs.append(a) + return interpret(i + 2, x, argv=argv, outs=outs) + # jump-if-true + elif op == '05': + a, b = x[i + 1], x[i + 2] + mode_a, mode_b = instruction[2], instruction[1] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + if a != 0: + return interpret(b, x, argv=argv, outs=outs) + else: + return interpret(i + 3, x, argv=argv, outs=outs) + # jump-if-false + elif op == '06': + a, b = x[i + 1], x[i + 2] + mode_a, mode_b = instruction[2], instruction[1] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + if a == 0: + return interpret(b, x, argv=argv, outs=outs) + else: + return interpret(i + 3, x, argv=argv, outs=outs) + pass + # less than + elif op == '07': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + if a < b: + x[out] = 1 + else: + x[out] = 0 + return interpret(i + 4, x, argv=argv, outs=outs) + # equals + elif op == '08': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + if a == b: + x[out] = 1 + else: + x[out] = 0 + return interpret(i + 4, x, argv=argv, outs=outs) + elif op == '99': + return x[0] + else: + raise Exception('Unsupported opcode: {}.'.format(op)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_6.py b/users/wpcarro/scratch/advent-of-code-2019/day_6.py new file mode 100644 index 000000000000..aba99b8239ff --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_6.py @@ -0,0 +1,155 @@ +from graphviz import Digraph + +data = """6WF)DRK 2PT)PSM H42)FN8 1XR)LQD HRK)9KL TD6)H8W 98Z)BJM RCQ)LVG +RWQ)Q7H 2PS)X94 NHB)25X PXC)W57 L8L)MVX CFK)D8K R1B)43T PDY)QKX FQK)82K JJ6)MQJ +FB6)6V1 R28)5MZ BN2)5HN 6BQ)JVC W57)22C MQJ)DL2 MTC)84R RH8)CRN Y27)3GN CKQ)31C +R7V)9BK ZDY)PDY X2Q)Y6S Q8B)SAN 1Z3)PVT R87)57R KCJ)44X PWQ)9CB HLC)VYW HFP)9XS +X33)MC3 RYS)R7R JRF)VHW 79R)FXZ YQQ)STV 8J6)JWX Q6D)RV6 LL9)B4D 6R1)T1Z VK9)42M +PQP)17N K6C)HMK GLY)N47 KDW)CDC DQ4)RY5 SND)FDR 7YF)1VN MDT)B3S D3F)98Z 5VH)MR7 +KNR)2L8 CJW)QDL FWY)14X SJD)79R COM)BXW T2B)FPB B2Q)BRJ Z21)HYC VHW)5XR WZ4)2JM +8HF)342 PYR)X9Y RKF)P43 S1S)9WT 2PB)BSB QF7)M9T HML)HMC 7J9)7Q6 8F1)29K DH1)NDM +1YC)PXC P32)HR7 PMX)7Y9 STV)SLW NYY)NF1 TG9)998 DMB)DLW XGL)1Z3 GK8)WCS YHR)HQC +9Q5)B6D R2T)CM5 6KC)J5G ZM9)L8L J8T)F89 3LN)YOU T2T)Z8F SCY)FKG 9W4)195 QLM)DD7 +4QY)JCB WKM)3JF 693)YM8 61M)B6Y DSP)X2M YZ5)DPL BC9)3B1 BDB)JTG 3TJ)TW1 W5M)SF6 +K4Q)X56 5HT)YHX YJG)DM5 68N)X2Q 2YP)DS5 BLK)MY3 6WV)VZ4 2JQ)ZT8 G93)V2W WN1)SBD +SS7)DY9 X56)8HP JY1)VS4 XQ6)L94 98Z)DMC V6S)NWT D9L)Y44 V6G)GVS JDW)FZW FJT)S38 +L2Z)VPL 7ZX)DKK X2M)8WM YVZ)XWS HMK)P87 47M)TD6 TDZ)21T 19R)95B GD9)Q1L 9QX)DFR +Y64)XGN CRG)6VY V3L)61D RJ4)C9Z XXG)P53 VJ8)QTF CPQ)2M9 JRN)8V1 KMH)K94 DLW)VQ4 +91W)2QQ G4B)RWQ 4P1)MKS K6G)DZ7 WCS)JR9 LXM)7RY 6ZB)K6G HMC)622 Z21)BLK Q6N)48V +66S)MK4 PDK)6WV Y6S)GY1 2L8)ZMG 42W)ZN6 6MS)8TZ JBY)STQ NSF)3ZM 5CV)X9N K4V)WFL +J6R)DT8 N3N)CX4 PTD)YXT F74)4T5 C51)3FW KRW)DS1 NWT)CKQ 195)6G6 HVQ)S18 Q7H)BKM +SKN)4D4 GK2)MLX MVX)TG9 YPK)RHQ Y9F)Z8W 42M)WNL 84R)6JP KNC)NHF FZW)PGM 3FW)HGX +DBK)FB6 45T)HLT L11)JVN HB5)K6C QH5)888 BTJ)J55 8BT)8ZS FR1)XGL S87)PS9 C4K)BN2 +N2Q)18C KTF)ZM9 TN2)B2Q DF3)CFK 9T3)TMR P29)3P1 P1W)7SQ 4D4)1DJ LML)ZJ3 Q4L)RKF +MW2)79T LVG)CPQ BDC)JH5 DNZ)232 998)GTM YGS)4WH GY1)C51 J55)QBT B8Z)34W FJ2)H42 +58J)326 T1Z)DCJ 1ZH)GLV 1YC)JG6 14K)22B RY5)QRY 7V2)2WT 4GQ)XHV ZJ3)TQ8 2G8)SN3 +FPB)HMN SC4)57D 5LQ)R2T LXM)R8Z JQ6)G4B WNL)GK2 42M)P75 LM3)YPK ZN6)753 PN4)835 +C4H)JY1 LR4)VD5 PSM)P1W VWL)C6C G2V)WBC 85M)R24 B1V)QW7 175)2PM Y1V)1ZH 34W)3MJ +WN7)TTB 3PV)CQD N7Y)9T3 223)8D4 RV6)LJ9 HFP)JRF VMT)DNB GJP)D3F J5G)KMS 7Q6)ZW2 +YCB)JBY XGN)MNL 888)DSP X61)Q6N WT5)X12 SDN)FD1 2QC)54W V98)964 T7S)YVZ MLX)9VZ +FR8)QH5 TVQ)2PS 2PV)FHY F4S)MPT 3J9)JNB J6M)GDC Q4C)MJN 9VZ)BZK P2P)B69 WBC)M1W +D97)HPF JKB)9L4 593)6YJ RMB)4Q5 QZB)38C H12)6R1 MKY)DDD HGX)CRG P53)WY7 22B)GMM +44X)2D8 DT8)L7H 3Y2)D3S FB8)68N 3BC)1XR 4XF)TVQ VPL)R7V Z4V)JSK B3S)FW5 49Z)YQQ +99V)D13 54Q)SS7 CYC)TXH PQ3)78W X4M)G9H WFL)M99 ZYY)3Y2 12Y)PSW W38)P29 H8W)JJ6 +P66)VPH GK2)45T H5F)FJT JDJ)SNV 14F)96Q JG6)TQ4 2L6)52Q SCY)CBJ 3GN)KNC KLM)XPR +DH1)QZB DMB)X7G DPL)7SX D97)N3N GNS)T95 53P)GW2 BHR)HNB YHX)XQV 2CR)Y1V C9D)Z7P +FN8)2PT 6LF)FCQ JNL)LQR SPV)YCB HGX)N83 VS4)8BT 5RH)FTX HYC)X2J 69V)J6S 9XS)PN4 +SD7)5Q3 2RN)82D QRY)FFY K2Y)3X2 79Z)S2Z YN2)Y64 JKB)MDT KJ8)NDH N57)5VH 3XK)1Q1 +SCH)FJ6 17N)GMP QR4)7V2 GLV)GLY NHF)ZDY QDL)S14 QF1)BMC ZLF)DHN 3JF)7TR MKS)GCY +964)91R 9L4)L5G RRX)6ZB CD7)73M 3X2)PGC HNB)S9Z L94)KLM 8MQ)SCR 18C)3TJ M4Y)BTJ +BC9)5YR TV5)SCY 2NX)8CC C9Z)MTC B69)3QP HR7)CHJ 8ZS)JRN 31C)TJW D43)4NH 93Q)X9X +T95)DNZ LQ5)BC9 9T5)S2C RP8)DH1 GCY)SD7 Y44)9B5 VG5)ZYY 7RY)V3L PWV)Q4L NF1)7YF +DRK)Y8V D13)GYG TW1)2PB ZVZ)2VV BRJ)V2V 9CB)Y7B MK4)9CJ TMR)6XS HWF)GK8 QTF)S1S +DFW)6LF N3S)WN1 N2Q)MSW CZ5)X61 FXZ)C4H SCQ)MF7 9LY)3LN 5MZ)PMX CN9)WF9 FHY)PR8 +S38)NWH M29)G5S 4NH)GZJ 5YR)54H CLX)MNY TJD)HQL RRZ)4GQ YHB)CZ5 P37)93Q YJG)3Q3 +95B)QMF CMQ)BLZ QD9)45M JSK)R28 YCW)CLX 8K3)JGB N8M)PQW P75)1HL XBS)T2T 22C)PVW +689)6MS FFY)RWX YHL)2G8 Y8V)4P1 Y7B)62Z YKJ)JDJ 1HL)5LQ PZ3)B1C 52Q)7HB 3Q2)ZV7 +YBF)Z4V J95)SDH NM6)YBF 8YN)J3M J6S)KNR PVT)N4X SDH)RFW RFW)7Y1 JCB)52B 3MJ)H58 +4QF)XHZ F62)DFW 7LJ)KDW JHL)C9D B4D)Q8B 342)YGS PFR)ZQT Z9K)TNS 8F8)WLB 94N)DMB +QBT)RYS 3VR)KRR 8D4)ST6 X9N)2PV 632)8K3 MX5)XNP 57D)Y27 18D)PQP D3F)RJ4 PLS)PBL +1JP)YDC 79V)BG2 S14)2NX 4Q5)NCQ FTX)555 2PM)KMH HQC)RMB 9Z9)BNZ XHV)Y94 7ZP)YHR +BNZ)49Z W6D)LX6 SLS)JL3 PVW)P9W Z1L)HB5 DS5)G2V Z9Q)RV8 DFR)LPJ 836)693 K94)VWL +HRG)836 J3V)593 52N)LPK 9KL)Y7M LX6)F7D JL3)511 L4G)D97 1RH)Y9F NJ2)LML GW2)9WV +8KZ)NRC XQV)G6D R8Z)QF7 326)HML R7R)8PM 622)YCW WQY)LGS NF1)FF3 5LQ)QF1 5XR)PTD +V2V)PFR 9T5)JQ6 CBQ)8KZ VZ4)HVQ TJW)DQT 9WT)5M6 CFK)YHL JR9)1JP Y1K)CF4 8WS)JPY +VYC)1D6 GKK)7J9 JTG)RRX 6V1)F74 1H5)QR4 SN3)NMG MF7)GQ1 RYK)SCH BNZ)9LY 1DJ)9LP +L6W)5BK FCQ)BFL DCJ)3RD MXD)8MQ RWX)1RH NBF)WKM K6C)WNH H58)L6W Y7B)BJH PGC)NBF +96Q)Q2W F7D)BSN 223)Z9K K94)VYC X9X)7M3 Q1M)3J9 QXF)XQ6 DD7)3Q2 Q1L)NHB 79T)LXQ +8TZ)M29 21T)Q4C B1C)NSF 8D8)FJ2 LJH)HGJ QS2)PS1 5KX)Z2L C6C)6BQ VQ2)2YP P87)N8M +ST5)L4G 8SP)W5M T4H)69V 9WF)GHS FF3)SND C5G)GKK VQ2)X4M P43)8J6 TD6)384 66V)CN9 +CX4)T9T NCQ)2JQ 29K)K8K RY5)K4Q GQ3)T4H FNH)P32 3BC)PRQ 5HN)4QY M1W)BGT 84R)ST5 +S45)CJW CK4)W7G SGX)19R S2C)7ZX DHN)W5Y 8D9)HM2 BSB)SPV D8K)DFV JHL)2L6 KYP)12Y +KDN)6X7 Y44)SQZ 6G6)SJD N7D)QGF Q84)8WJ F89)LL9 LYJ)2RN 25X)Q84 HM3)53P JNB)QD9 +SLW)1DQ 384)3BC PR8)NGV 49N)7ZP 65H)LHJ 6XS)S45 ZMG)FR1 X2M)Y86 QD3)QLM P4R)PQ3 +RTK)4M3 4YW)N7D R7V)M4M 73M)CBF DFV)64R Z7P)LMK HRG)Y1K 3ZM)BCZ WY7)QXP DMC)9Q5 +PSW)1H5 8CC)TV5 TTB)S88 BZK)K2Y T2B)CBQ HJB)Y19 DQW)KML Z8W)8ZL PBL)5TK 1D6)MX5 +3MJ)4YW MDT)HJB 62Z)X33 DZ7)BDC 9CJ)FRD 82D)KDN LK7)18D 9QQ)61M Y34)DZG J4T)6KC +971)QD3 511)GQ3 MJN)F62 RNM)NKG BGW)KJ8 DL2)1YH ZQT)RYZ 1YH)ZJ6 2WT)YYQ 7HB)DYQ +3BN)WQY 2M9)62D TSK)YR1 N7Y)VJ8 WZ4)FWT MNY)YN2 DYQ)RRZ 3RG)YT3 2SM)VK9 JH5)ZXH +GYG)K2M PKF)V6G JGB)S87 X94)N57 MSW)L2Z X4N)25G BLZ)4QF JPY)GD9 WLB)V6S KML)2SM +TXH)9X1 48V)KTR 8PM)WZ4 ZW2)967 PS9)3BN 4WH)9T5 8M1)R6V N7M)VWK S88)978 N4X)8KH +6VY)PLS NRC)874 QGF)QWJ NMG)J3V B8Z)WPF 45M)2QC KDW)VQ2 FZW)223 BXW)QXF FRD)PWV +8HP)4G7 KDN)YYL LHJ)SDN P6P)XMC W5Y)RYK HX8)KW3 Z2L)H12 WPF)T2B L7H)BGW MNL)17B +GHS)66V QKX)XWV FW5)W38 PDK)Y34 FKG)Q6D DQT)YJG 15G)79V 4VK)51Y BJH)LR4 48V)6GC +DM5)Y1F CM5)VG5 KB8)HRK 5HN)RCQ 6JP)SDQ LGH)NJ2 L94)N7Y 4Y2)ZLF 25G)C4K K8K)SLS +232)ZVZ GQ1)58J RV8)H5F 78W)565 YCF)8D9 DZG)99V N83)CKR TN2)ZCX NGV)8SP BSN)FTN +LPJ)94N 3Q3)Q1M JVX)971 54W)LGH 67Y)P66 R24)P37 3QP)QTY YHR)FLT GMP)NM6 NDH)632 +PWV)8D8 LMK)3PV ZWJ)KB8 967)4VK 3B1)WN7 XWS)5CV YR1)FNH 565)4PH 5BK)V98 W5Y)FR8 +PS1)HX8 38C)XXG XWV)1YC M4M)LQ5 S9Z)49N XMC)R1B YYL)VC9 GMM)SCQ LXQ)J95 51Y)RP8 +HLT)XBS 82K)B8Z NR5)7K3 K2M)67Y SF6)W6D CF4)85M MC3)LXM HMN)RNM BFL)4XF MT2)PM4 +VWK)JKB 3JF)ZTZ QWJ)9QQ KRR)TJD VYW)Z9Q CK4)QS2 8NQ)NR5 57R)BHR 8WM)YHB Y86)GNS +2Y2)Z21 X12)9QX LJ9)YKJ 3RD)8F1 7SQ)CK4 ZXH)3XK DDD)5KX ZCX)PYR GZJ)KXL KC5)52N +PM4)RYP 14X)ZWJ FJ6)175 17B)689 HQL)14F LQR)DBK LGS)4Y2 2QQ)SGR 2VV)8F8 J6S)LM3 +RTP)YZ5 XDD)14K VQ4)MT2 KMH)KYC CKR)RTP VD5)MRM CM5)KRW BG3)XDD PGM)J4T MY3)JVX +Z8F)WNP BKM)WT5 FLT)KTF N7D)8M1 Y19)CMQ HPF)WDL 65H)JJP 2MQ)66S 4Q5)54Q Q2W)ZL4 +QTY)659 MRM)9Z9 X2J)SC4 YWH)RB3 FTN)LYJ LMK)N7M SGX)15G KW3)FQK 3VV)JNL JWX)R8R +9Z3)9MB BMC)N3S W7G)Z1L SD7)MW2 376)RH8 NWT)JHL 7CD)N2Z KTR)HM3 1Q1)TDZ DY9)2CR +6YJ)14G FWT)JDW C2S)C5G SNV)J6M 5TK)YWH J3M)8HF HM2)GJP P9W)7CD 1VN)SGX KMS)RBK +64R)B1V 62D)3VV 61D)F4S XPR)SKN FJT)N3P 9WV)D43 TQ8)BDB 46H)K4V 8WJ)MXD NDM)9WF +8ZL)1QJ SCR)2MQ 7Y9)LJH VPH)MKY YDC)PDK 4G7)65H 2JM)NYY T9T)VMT 8M1)TSK G5S)X4N +6FH)KYP D98)DQW G6D)C2S 6X7)N2Q 1QJ)T7S ZL4)J8T 5BT)3VR 835)KCJ YM8)3RG Y7M)PWQ +54W)9W4 CBF)7LJ 4T5)8WS RHQ)HBK CQD)D98 HGJ)J6R JVC)79Z FD1)PKF VC9)5BT C4H)6WF +D3S)P6P MR7)BG3 R6V)DF3 9X1)NQ5 ZTZ)2Y2 8WM)HFP CDC)376 TQ4)M4Y 9MB)N1R HBK)DQ4 +1DQ)CYC WNP)DM8 CBJ)LK7 ZT8)FWY LQD)PNN 555)9Z3 TNS)D9L QMF)L11 FR8)5RH WF9)R87 +NKG)5HT L5G)91W N2Z)YV9 9B5)CD7 ZV7)8NQ ST6)74T ZJ6)CQV S18)47M 74T)8YN WNH)TN2 +874)46H 3VV)PZ3 Y1F)42W MPT)2LP FDR)HWF X7G)RTK 52B)P4R RYP)G93 NWH)YCF 7TR)FB8 +RWQ)6FH 8F8)HLC CRN)P2P B6D)KC5 PNN)HRG""".split() + +# COM is the root in this tree + + +# parent :: Vertex -> [Edge] -> Maybe(Vertex) +def parent(x, xs): + for a, b in xs: + if b == x: + return a + return None + + +# parents :: Vertex -> [Edge] -> [Vertex] +def parents(x, xs): + parents = [] + p = parent(x, xs) + while p: + parents.append(p) + p = parent(p, xs) + return parents + + +# alias Vertex :: String +# alias Edge :: (String, String) +# to_edge_list :: [String] -> [(String, String)] +def to_edge_list(xs): + """Returns a list of tuples where (A, B) represents a directed edge from + vertex A to vertex B.""" + return [(x[0:3], x[4:]) for x in xs] + + +# to_graphviz :: [Edge] -> String +def to_graphviz(xs): + d = Digraph() + for a, b in xs: + d.node(a, label=a) + d.edge(a, b) + return d.source + + +graph = to_edge_list(data) +you = parents('YOU', graph) +san = parents('SAN', graph) + +# Distance from YOU to shared point with SAN +yd = 1 +for i in range(len(you)): + if you[i] in san: + break + yd += 1 + +# Distance from SAN to shared point with YOU +sd = 1 +for i in range(len(san)): + if san[i] in you: + break + sd += 1 + +print('Number of orbital transfers required: {}'.format(yd - 1 + sd - 1)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_7.py b/users/wpcarro/scratch/advent-of-code-2019/day_7.py new file mode 100644 index 000000000000..14597d5104e3 --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_7.py @@ -0,0 +1,49 @@ +from day_5 import interpret +from itertools import permutations + +# TODO: I may need to re-write this in Elixir modelling each amplifier as a +# `Process` and `Process.send`ing each amplifier the signals. + +data = [ + 3, 8, 1001, 8, 10, 8, 105, 1, 0, 0, 21, 38, 59, 76, 89, 106, 187, 268, 349, + 430, 99999, 3, 9, 1002, 9, 3, 9, 101, 2, 9, 9, 1002, 9, 4, 9, 4, 9, 99, 3, + 9, 1001, 9, 5, 9, 1002, 9, 5, 9, 1001, 9, 2, 9, 1002, 9, 3, 9, 4, 9, 99, 3, + 9, 1001, 9, 4, 9, 102, 4, 9, 9, 1001, 9, 3, 9, 4, 9, 99, 3, 9, 101, 4, 9, + 9, 1002, 9, 5, 9, 4, 9, 99, 3, 9, 1002, 9, 3, 9, 101, 5, 9, 9, 1002, 9, 3, + 9, 4, 9, 99, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, + 1002, 9, 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, + 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, + 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 99, 3, 9, 1002, 9, + 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, + 1, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, + 101, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, + 9, 1001, 9, 2, 9, 4, 9, 99, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, + 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, + 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, + 9, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 99, 3, 9, + 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, + 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, + 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, 1, 9, + 9, 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 99, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 102, + 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, + 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, 9, + 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, 9, 9, + 4, 9, 99 +] + +data_a, data_b, data_c, data_d, data_e = data[:], data[:], data[:], data[:], data[:] + +# m = 0 +# for a, b, c, d, e in permutations(range(5, 10)): +# answer = None +# z = 0 +# while z is not None: +# print(a, b, c, d, e) +# print('---') +# v = interpret(0, data_a, argv=[a, z]) +# print(v) +# w = interpret(0, data_b, argv=[b, v]) +# x = interpret(0, data_c, argv=[c, w]) +# y = interpret(0, data_d, argv=[d, x]) +# z = interpret(0, data_e, argv=[e, y]) +# m = max(m, z) diff --git a/users/wpcarro/scratch/crack_the_coding_interview/11_1.py b/users/wpcarro/scratch/crack_the_coding_interview/11_1.py new file mode 100644 index 000000000000..ec7b65dae0c3 --- /dev/null +++ b/users/wpcarro/scratch/crack_the_coding_interview/11_1.py @@ -0,0 +1,40 @@ +# Implementation for a problem from "Crack the Coding Interview". +# +# Dependencies: +# - python 2.7.16 +# - entr 4.1 +# +# To run the tests, run: `python 11_1.py` +# For a tight development loop, run: `echo 11_1.py | entr python /_` +# +# Author: William Carroll <wpcarro@gmail.com> + +################################################################################ +# Implementation +################################################################################ +def insert_sorted(xs, ys): + """ + Merges `ys` into `xs` and ensures that the result is sorted. + + Assumptions: + - `xs` and `ys` are both sorted. + - `xs` has enough unused space to accommodate each element in `ys`. + """ + for y in ys: + xi = xs.index(None) - 1 + yi = xs.index(None) + xs[yi] = y + while xi != -1 and y < xs[xi]: + xs[xi], xs[yi] = xs[yi], xs[xi] + xi, yi = xi - 1, yi - 1 + return xs + +################################################################################ +# Tests +################################################################################ +assert insert_sorted([1, 3, 5, None, None], [2, 4]) == [1, 2, 3, 4, 5] +assert insert_sorted([None, None], [2, 4]) == [2, 4] +assert insert_sorted([None, None], [2, 4]) == [2, 4] +assert insert_sorted([1, 1, None, None], [0, 0]) == [0, 0, 1, 1] +assert insert_sorted([1, 1, None, None], [1, 1]) == [1, 1, 1, 1] +print('All tests pass!') diff --git a/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs b/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs new file mode 100644 index 000000000000..8496d88c0c0c --- /dev/null +++ b/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs @@ -0,0 +1,11 @@ +data Tree a = Node a [Tree a] deriving (Show) + +withRoot :: [a] -> [Tree a] +withRoot xs = xs |> toThing |> fmap buildTree + +buildTree :: (a, [a]) + + +toTree :: [a] -> Tree a +toTree [x] = Node x [] +toTree [x | xs] = Node x (toTree xs) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py b/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py new file mode 100644 index 000000000000..35cb4392812e --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py @@ -0,0 +1,87 @@ +# This is practice for various types of list traversals that turn up. + +xs = range(10) +n = len(xs) + +print('---') +# pythonic left-to-right traversal +result = '' +for x in xs: + result += str(x) +print(result) + +print('---') +# left-to-right traversal +result = '' +for i in range(n): + result += str(xs[i]) +print(result) + +print('---') +# right-to-left traversal +result = '' +for i in range(n): + result += str(xs[n - 1 - i]) +print(result) + +print('---') +# 2x left-to-right traversal +result = '' +for i in range(2 * n): + result += str(xs[i % n]) +print(result) + +print('---') +# 2x right-to-left traversal +result = '' +for i in range(2 * n): + result += str(xs[(n - 1 - i) % n]) +print(result) + +################################################################################ +# Table traversals +################################################################################ + +table = [[row * 10 + i for i in range(10)] for row in range(3)] +row_ct = len(table) +col_ct = len(table[0]) + +print('---') +# 3x10 table traversal +result = '' +for row in table: + r = '' + for col in row: + r += '{:3d}'.format(col) + result += r + '\n' +print(result[0:-1]) + +print('---') +# 3x10 table traversal +result = '' +for row in range(row_ct): + r = '' + for col in range(col_ct): + r += '{:3d}'.format(table[row][col]) + result += r + '\n' +print(result[0:-1]) + +print('---') +# 3x10 table traversal (reverse) +result = '' +for row in range(row_ct): + r = '' + for col in range(col_ct): + r += '{:3d}'.format(table[row_ct - 1 - row][col_ct - 1 - col]) + result += r + '\n' +print(result) + +print('---') +# 3x10 column-row traversal +result = '' +for col in range(col_ct): + r = '' + for row in range(row_ct): + r += '{:3d}'.format(table[row][col]) + result += r + '\n' +print(result) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py b/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py new file mode 100644 index 000000000000..01fd965fd540 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py @@ -0,0 +1,145 @@ +import unittest +from itertools import combinations + + +def balanced(xs): + """Return True if `xs` contains no two values that differ by more than + one.""" + if len(xs) == 0 or len(xs) == 1: + return True + if len(xs) == 2: + return math.abs(xs[0] - xs[1]) <= 1 + else: + pass + + +def is_leaf(node): + return node.left is None and node.right is None + + +def is_balanced(tree_root): + """Returns True if the difference between the depths of any two leaf nodes + does not exceed 1.""" + depths = set() + populate_depths(tree_root, 0, depths) + + # cartesian product - only the top half + for diff in set(abs(a - b) for a, b in combinations(depths, 2)): + if diff > 1: + return False + + return True + + +def populate_depths(node, depth, depths): + if is_leaf(node): + depths.add(depth) + else: + if node.left is not None: + populate_depths(node.left, depth + 1, depths) + if node.right is not None: + populate_depths(node.right, depth + 1, depths) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py b/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py new file mode 100644 index 000000000000..dc30bb508887 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py @@ -0,0 +1,32 @@ +def test(x, i): + return x & (1 << i) != 0 + + +def set(x, i): + return x | (1 << i) + + +def clear(x, i): + return x & ~(1 << i) + + +def toggle(x, i): + if test(x, i): + return clear(x, i) + else: + return set(x, i) + + +def test_single(x): + if x == 0: + return False + else: + return x & (x - 1) == 0 + + +print(test(0b1010, 3)) +print('{0:b}'.format(set(0b1010, 1))) +print('{0:b}'.format(clear(0b1010, 1))) +print('{0:b}'.format(toggle(0b1010, 2))) +print(test_single(0b1010)) +print(test_single(0b1000)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py b/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py new file mode 100644 index 000000000000..a50f8b074e55 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py @@ -0,0 +1,63 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# is_valid :: String -> Boolean +def is_valid(xs): + s = [] + seeking = { + '}': '{', + ']': '[', + ')': '(', + } + openers = seeking.values() + closers = seeking.keys() + for c in xs: + if c in openers: + s.append(c) + elif c in closers: + if not s: + return False + elif s[-1] != seeking.get(c): + return False + else: + s.pop() + return len(s) == 0 + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_valid_short_code(self): + result = is_valid('()') + self.assertTrue(result) + + def test_valid_longer_code(self): + result = is_valid('([]{[]})[]{{}()}') + self.assertTrue(result) + + def test_interleaved_openers_and_closers(self): + result = is_valid('([)]') + self.assertFalse(result) + + def test_mismatched_opener_and_closer(self): + result = is_valid('([][]}') + self.assertFalse(result) + + def test_missing_closer(self): + result = is_valid('[[]()') + self.assertFalse(result) + + def test_extra_closer(self): + result = is_valid('[[]]())') + self.assertFalse(result) + + def test_empty_string(self): + result = is_valid('') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py b/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py new file mode 100644 index 000000000000..689be97a8503 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py @@ -0,0 +1,121 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +# is_leaf :: Node(a) -> Boolean +def is_leaf(node): + return not node.left and not node.right + + +# is_binary_search_tree :: Node(Integer) -> Set(Int) -> Set(Int) -> Boolean +def is_binary_search_tree_a(node, la=set(), ra=set()): + """My first solution for this problem.""" + for x in la: + if not node.value < x: + return False + for x in ra: + if not node.value > x: + return False + if is_leaf(node): + return True + elif not node.left: + return is_binary_search_tree( + node.right, + la=la, + ra=ra ^ {node.value}, + ) + elif not node.right: + return is_binary_search_tree(node.left, la=la ^ {node.value}, ra=ra) + else: + return all([ + is_binary_search_tree(node.left, la=la ^ {node.value}, ra=ra), + is_binary_search_tree(node.right, la=la, ra=ra ^ {node.value}) + ]) + + +# is_binary_search_tree :: Node(Int) -> Maybe(Int) -> Maybe(Int) -> Boolean +def is_binary_search_tree(node, lb=None, ub=None): + if lb: + if node.value < lb: + return False + if ub: + if node.value > ub: + return False + if is_leaf(node): + return True + elif not node.right: + return is_binary_search_tree(node.left, lb=lb, ub=node.value) + elif not node.left: + return is_binary_search_tree(node.right, lb=node.value, ub=ub) + else: + return is_binary_search_tree( + node.left, lb=lb, ub=node.value) and is_binary_search_tree( + node.right, lb=node.value, ub=ub) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_valid_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_both_subtrees_valid(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(80) + left.insert_left(20) + left.insert_right(60) + right.insert_left(70) + right.insert_right(90) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_out_of_order_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(70) + right_right = right.insert_right(60) + right_right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_one_node_tree(self): + tree = Test.BinaryTreeNode(50) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py b/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py new file mode 100644 index 000000000000..e34a2b136ab6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py @@ -0,0 +1,91 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +def is_first_come_first_served(to, di, xs): + # All the guards, assertions we should need. + if to == di == xs == []: + return True + elif to == di == []: + return False + elif to == []: + return di == xs + elif to == []: + return di == xs + elif di == []: + return to == xs + elif xs == []: + return False + elif len(xs) != (len(to) + len(di)): + return False + + fst, snd = to, di + + if xs[0] == to[0]: + fst, snd = to, di + elif xs[0] == di[0]: + fst, snd = di, to + else: + return False + + fst_done, snd_done = False, False + fi, si = 1, 0 + + for i in range(1, len(xs)): + # Short-circuit and avoid index-out-of-bounds without introducing overly + # defensive, sloppy code. + if fst_done: + return snd[si:] == xs[i:] + elif snd_done: + return fst[fi:] == xs[i:] + + if fst[fi] == xs[i]: + fi += 1 + elif snd[si] == xs[i]: + si += 1 + else: + return False + + fst_done, snd_done = fi == len(fst), si == len(snd) + + return True + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_both_registers_have_same_number_of_orders(self): + result = is_first_come_first_served([1, 4, 5], [2, 3, 6], + [1, 2, 3, 4, 5, 6]) + self.assertTrue(result) + + def test_registers_have_different_lengths(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 2, 6, 3, 5]) + self.assertFalse(result) + + def test_one_register_is_empty(self): + result = is_first_come_first_served([], [2, 3, 6], [2, 3, 6]) + self.assertTrue(result) + + def test_served_orders_is_missing_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 6, 3, 5]) + self.assertFalse(result) + + def test_served_orders_has_extra_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], + [1, 2, 3, 5, 6, 8]) + self.assertFalse(result) + + def test_one_register_has_extra_orders(self): + result = is_first_come_first_served([1, 9], [7, 8], [1, 7, 8]) + self.assertFalse(result) + + def test_one_register_has_unserved_orders(self): + result = is_first_come_first_served([55, 9], [7, 8], [1, 7, 8, 9]) + self.assertFalse(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py b/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py new file mode 100644 index 000000000000..9eddb34b2db3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py @@ -0,0 +1,71 @@ +import unittest +from math import floor + + +################################################################################ +# Solution +################################################################################ +def max_duffel_bag_value(xs, cap): + ct = (cap + 1) + maxes = [0] * ct + for c in range(cap + 1): + for w, v in xs: + if w == 0 and v > 0: + return float('inf') + if w == c: + maxes[c:] = [max(maxes[c], v)] * (ct - c) + elif w < c: + d = c - w + maxes[c:] = [max(maxes[c], v + maxes[d])] * (ct - c) + else: + continue + return maxes[cap] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_cake(self): + actual = max_duffel_bag_value([(2, 1)], 9) + expected = 4 + self.assertEqual(actual, expected) + + def test_two_cakes(self): + actual = max_duffel_bag_value([(4, 4), (5, 5)], 9) + expected = 9 + self.assertEqual(actual, expected) + + def test_only_take_less_valuable_cake(self): + actual = max_duffel_bag_value([(4, 4), (5, 5)], 12) + expected = 12 + self.assertEqual(actual, expected) + + def test_lots_of_cakes(self): + actual = max_duffel_bag_value([(2, 3), (3, 6), (5, 1), (6, 1), (7, 1), + (8, 1)], 7) + expected = 12 + self.assertEqual(actual, expected) + + def test_value_to_weight_ratio_is_not_optimal(self): + actual = max_duffel_bag_value([(51, 52), (50, 50)], 100) + expected = 100 + self.assertEqual(actual, expected) + + def test_zero_capacity(self): + actual = max_duffel_bag_value([(1, 2)], 0) + expected = 0 + self.assertEqual(actual, expected) + + def test_cake_with_zero_value_and_weight(self): + actual = max_duffel_bag_value([(0, 0), (2, 1)], 7) + expected = 3 + self.assertEqual(actual, expected) + + def test_cake_with_non_zero_value_and_zero_weight(self): + actual = max_duffel_bag_value([(0, 5)], 5) + expected = float('inf') + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/coins.py b/users/wpcarro/scratch/data_structures_and_algorithms/coins.py new file mode 100644 index 000000000000..eb5754f98210 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/coins.py @@ -0,0 +1,57 @@ +import unittest +from math import floor + +################################################################################ +# Solution +################################################################################ + +# change_possibilities :: Int -> [Int] -> Int +def change_possibilities(n, xs): + combinations = [0] * (n + 1) + combinations[0] = 1 + + for x in xs: + for i in range(len(combinations)): + if i >= x: + combinations[i] += combinations[i - x] + + return combinations[n] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + + def test_sample_input(self): + actual = change_possibilities(4, (1, 2, 3)) + expected = 4 + self.assertEqual(actual, expected) + + def test_one_way_to_make_zero_cents(self): + actual = change_possibilities(0, (1, 2)) + expected = 1 + self.assertEqual(actual, expected) + + def test_no_ways_if_no_coins(self): + actual = change_possibilities(1, ()) + expected = 0 + self.assertEqual(actual, expected) + + def test_big_coin_value(self): + actual = change_possibilities(5, (25, 50)) + expected = 0 + self.assertEqual(actual, expected) + + def test_big_target_amount(self): + actual = change_possibilities(50, (5, 10)) + expected = 6 + self.assertEqual(actual, expected) + + def test_change_for_one_dollar(self): + actual = change_possibilities(100, (1, 5, 10, 25, 50)) + expected = 292 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py b/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py new file mode 100644 index 000000000000..3836bcd0c653 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py @@ -0,0 +1,78 @@ +from itertools import product +from random import choice +from time import sleep +from os import system +from math import floor +from colorama import Back, Fore, Style + +################################################################################ +# Simulation of Conway's Game of Life. The goal here was to write this with a +# small amount of code as a proof-of-concept that could be run in the terminal. +# +# If you'd like to tinker with the rules, see the conditionals defined in the +# `advance/1` function. For other parameters, like the board size and refresh +# rate, refer to the while-loop defined at the bottom of this file. +################################################################################ + + +def init_board(n, init_alive_percentage): + """Initialize a board of size `n` by `n`. Supply a percentage, + `init_alive_percentage`, representing the number of cells in the board that + should be alive from the start.""" + alive_count = floor(n * init_alive_percentage) + distribution = [True] * alive_count + [False] * (n - alive_count) + return [[choice(distribution) for _ in range(n)] for _ in range(n)] + + +def neighbors(coord, board): + """Return the neighbors for a given `coord` on a `board`.""" + n = len(board) + row, col = coord + return [ + board[(row + row_d) % n][(col + col_d) % n] + for row_d, col_d in product([-1, 0, 1], [-1, 0, 1]) + if (row_d, col_d) != (0, 0) + ] + + +def advance(board): + """Advance the state of the `board` from T[n] to T[n+1].""" + n = len(board) + new_board = [[False for _ in range(n)] for _ in range(n)] + for row in range(n): + for col in range(n): + alive_count = len([x for x in neighbors((row, col), board) if x]) + # Loneliness + if alive_count == 0: + new_board[row][col] = False + # Status Quo + elif alive_count == 1: + new_board[row][col] = board[row][col] + # Cooperation + elif alive_count == 2: + new_board[row][col] = True + # Resource starvation + elif alive_count >= 3: + new_board[row][col] = False + return new_board + + +def print_board(board): + """Print the game `board` in a human-readable way.""" + result = '' + for row in board: + for col in row: + if col: + result += Back.GREEN + '1 ' + Style.RESET_ALL + else: + result += Back.RED + '0 ' + Style.RESET_ALL + result += '\n' + print(result) + + +board = init_board(100, 0.50) +while True: + system('clear') + print_board(board) + sleep(0.15) + board = advance(board) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py b/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py new file mode 100644 index 000000000000..7e431e224962 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py @@ -0,0 +1,60 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def delete_node(x): + if not x.next: + raise Exception('Cannot delete the last node in a linked list.') + else: + x.value = x.next.value + x.next = x.next.next + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_node_at_beginning(self): + delete_node(self.first) + actual = self.first.get_values() + expected = [2, 3, 4] + self.assertEqual(actual, expected) + + def test_node_in_middle(self): + delete_node(self.second) + actual = self.first.get_values() + expected = [1, 3, 4] + self.assertEqual(actual, expected) + + def test_node_at_end(self): + with self.assertRaises(Exception): + delete_node(self.fourth) + + def test_one_node_in_list(self): + unique = Test.LinkedListNode(1) + with self.assertRaises(Exception): + delete_node(unique) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/dft.py b/users/wpcarro/scratch/data_structures_and_algorithms/dft.py new file mode 100644 index 000000000000..127d48c1864b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/dft.py @@ -0,0 +1,65 @@ +from random import choice + + +class Node(object): + def __init__(self, value=None, left=None, right=None): + self.value = value + self.left = left + self.right = left + + +def p(node, indent=0): + print(indent * ' ' + '|-' + str(node.value)) + if node.left is not None: + p(node.left, indent=indent + 2) + if node.right is not None: + p(node.right, indent=indent + 2) + + +# read trees (i.e. traversing, parsing) +# write trees (i.e. generating, printing) +def random(d=0): + left = None + right = None + + if choice([True, False]): + left = random(d + 1) + + if choice([True, False]): + right = random(d + 1) + + return Node( + value=d, + left=left, + right=right, + ) + + +################################################################################ +# DFTs can be: +# - imperative (mutable) +# - functional (immutable) +# - iterative +# - recursive +################################################################################ + + +# Iterative +def traverse(node, f): + stack = [(node, 0)] + + while len(stack): + node, depth = stack.pop() + f(node, depth) + print(depth) + + if node.left is not None: + stack.append((node.left, depth + 1)) + if node.right is not None: + stack.append((node.right, depth + 1)) + + +print('----------------------------------------------------------------------') +for _ in range(10): + traverse(random(), lambda _, d: print(d)) +print() diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py b/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py new file mode 100644 index 000000000000..03907f604044 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py @@ -0,0 +1,48 @@ +from collections import deque +from heapq import heappush, heappop +from fixtures import weighted_graph + + +def put(t, x, xs): + if t == 'stack': + return xs.append(x) + if t == 'queue': + return xs.append(x) + if t == 'priority': + return heappush(xs, x) + + +def pop(t, xs): + if t == 'stack': + return xs.pop() + if t == 'queue': + return xs.popleft() + if t == 'priority': + return heappop(xs) + + +# shortest_path :: Vertex -> Vertex -> Graph -> [Vertex] +def shortest_path(a, b, g): + """Returns the shortest path from vertex a to vertex b in graph g.""" + t = 'priority' + xs = [] + seen = set() + # Map(Weight, [Vertex]) + m = {} + + put(t, (0, [a], a), xs) + + while xs: + w0, path, v = pop(t, xs) + + seen.add(v) + if v == b: + m[w0] = path + for w1, x in g.get(v): + if x not in seen: + put(t, (w0 + w1, path + [x], x), xs) + + return m + + +print(shortest_path('a', 'f', graph_a)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py new file mode 100644 index 000000000000..93fdd9eed2d6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py @@ -0,0 +1,56 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_duplicate(xs): + self_ref_count = 0 + for i in range(len(xs)): + if xs[i] == i + 1: + self_ref_count += 1 + hops = len(xs) - 1 - self_ref_count + current = xs[-1] + while hops > 0: + current = xs[current - 1] + hops -= 1 + return current + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + # TODO: Debug why this fails. + def test_darren_from_interview_cake(self): + actual = find_duplicate([4, 1, 8, 3, 2, 7, 6, 5, 4]) + expected = 4 + self.assertEqual(actual, expected) + + def test_just_the_repeated_number(self): + actual = find_duplicate([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + actual = find_duplicate([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_last_cycle(self): + actual = find_duplicate([3, 4, 2, 3, 1, 5]) + expected = 3 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_duplicate([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_duplicate([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py new file mode 100644 index 000000000000..e2739f0f6055 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py @@ -0,0 +1,61 @@ +from math import floor +import unittest + + +################################################################################ +# Solution +################################################################################ +def bounds(r): + ct = len(r) + if ct % 2 == 0: + h = int(ct / 2) + return ct, h + else: + h = floor(ct / 2) + return ct, h + + +def find_repeat(xs): + ct, h = bounds(xs) + rl = range(1, h + 1) + rr = range(h + 1, ct) + while True: + nl = len([None for x in xs if x in rl]) + nr = len([None for x in xs if x in rr]) + branch = rl if nl > nr else rr + if len(branch) == 1: + return branch[0] + ct, h = bounds(branch) + rl = range(branch[0], branch[0]) + rr = range(branch[0] + h, branch[-1] + 1) + raise Exception( + 'We could not find any duplicates in xs. Perhaps xs did not adhere to the usage contract.' + ) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_just_the_repeated_number(self): + actual = find_repeat([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + actual = find_repeat([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py new file mode 100644 index 000000000000..2103a5b84f75 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py @@ -0,0 +1,59 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_rotation_point(xs): + """Usage of `visited` here is a hack, but works for the test cases + (gulp).""" + i = 0 + j = round(len(xs) / 2) + result = None + visited = set() + while not result: + if i in visited: + i += 1 + if j in visited: + j -= 1 + visited.add(i) + visited.add(j) + if xs[j - 1] > xs[j]: + result = j + elif xs[i] < xs[j]: + i = j + j += round((len(xs) - j) / 2) + elif xs[i] >= xs[j]: + i = j + j -= round((j - i) / 2) + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_small_list(self): + actual = find_rotation_point(['cape', 'cake']) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_rotation_point( + ['grape', 'orange', 'plum', 'radish', 'apple']) + expected = 4 + self.assertEqual(actual, expected) + + def test_large_list(self): + actual = find_rotation_point([ + 'ptolemaic', 'retrograde', 'supplant', 'undulate', 'xenoepist', + 'asymptote', 'babka', 'banoffee', 'engender', 'karpatka', + 'othellolagkage' + ]) + expected = 5 + self.assertEqual(actual, expected) + + # Are we missing any edge cases? + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py new file mode 100644 index 000000000000..dfa5de42cc0b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py @@ -0,0 +1,45 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_unique_delivery_id(xs): + a = 0 + for x in xs: + a ^= x + return a + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_drone(self): + actual = find_unique_delivery_id([1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_comes_first(self): + actual = find_unique_delivery_id([1, 2, 2]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_comes_last(self): + actual = find_unique_delivery_id([3, 3, 2, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_in_middle(self): + actual = find_unique_delivery_id([3, 2, 1, 2, 3]) + expected = 1 + self.assertEqual(actual, expected) + + def test_many_drones(self): + actual = find_unique_delivery_id( + [2, 5, 4, 8, 6, 3, 1, 4, 2, 3, 6, 5, 1]) + expected = 8 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py b/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py new file mode 100644 index 000000000000..27689ca76d04 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py @@ -0,0 +1,110 @@ +# Using this module to store commonly used, but annoying to create, data +# structures for my test inputs. +# +# Use like: +# from fixtures import graph_a + +################################################################################ +# Constants +################################################################################ + +edge_list = [ + ('a', 'b'), + ('a', 'c'), + ('a', 'e'), + ('b', 'c'), + ('b', 'd'), + ('c', 'e'), + ('d', 'f'), + ('e', 'd'), + ('e', 'f'), +] + +unweighted_graph = { + 'a': {'b', 'c', 'e'}, + 'b': {'c', 'd'}, + 'c': {'e'}, + 'd': {'f'}, + 'e': {'d', 'f'}, + 'f': set(), +} + +adjacencies = { + 'a': { + 'a': False, + 'b': False + }, + 'a': [], + 'a': [], + 'a': [], + 'a': [], + 'a': [], + 'a': [], +} + +weighted_graph = { + 'a': {(4, 'b'), (2, 'c'), (4, 'e')}, + 'b': {(5, 'c'), (10, 'd')}, + 'c': {(3, 'e')}, + 'd': {(11, 'f')}, + 'e': {(4, 'd'), (5, 'f')}, + 'f': set(), +} + +# This is `weighted_graph` with each of its weighted edges "expanded". +expanded_weights_graph = { + 'a': ['b-1', 'c-1', 'e-1'], + 'b-1': ['b-2'], + 'b-2': ['b-3'], + 'b-3': ['b'], + 'c-1': ['c'], + 'e-1': ['e-2'], + 'e-2': ['e-3'], + 'e-3': ['e'], + # and so on... +} + +unweighted_digraph = { + '5': {'2', '0'}, + '4': {'0', '1'}, + '3': {'1'}, + '2': {'3'}, + '1': set(), + '0': set(), +} + +################################################################################ +# Functions +################################################################################ + + +def vertices(xs): + result = set() + for a, b in xs: + result.add(a) + result.add(b) + return result + + +def edges_to_neighbors(xs): + result = {v: set() for v in vertices(xs)} + for a, b in xs: + result[a].add(b) + return result + + +def neighbors_to_edges(xs): + result = [] + for k, ys in xs.items(): + for y in ys: + result.append((k, y)) + return result + + +def edges_to_adjacencies(xs): + return xs + + +# Skipping handling adjacencies because I cannot think of a reasonable use-case +# for it when the vertex labels are items other than integers. I can think of +# ways of handling this, but none excite me. diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py b/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py new file mode 100644 index 000000000000..bc7f7ceea562 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py @@ -0,0 +1,180 @@ +import unittest +from collections import deque + + +################################################################################ +# Solution +################################################################################ +class GraphNode: + def __init__(self, label): + self.label = label + self.neighbors = set() + self.color = None + + +# color_graph :: G(V, E) -> Set(Color) -> IO () +def color_graph(graph, colors): + q = deque() + seen = set() + q.append(graph[0]) + + while q: + node = q.popleft() + + illegal = {n.color for n in node.neighbors} + for x in colors: + if x not in illegal: + node.color = x + + seen.add(node) + + for x in node.neighbors: + if x not in seen: + q.append(x) + + # TODO: Is this the best way to traverse separate graphs? + for x in graph: + if x not in seen: + q.append(x) + + return 0 + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def setUp(self): + self.colors = frozenset([ + 'red', + 'green', + 'blue', + 'orange', + 'yellow', + 'white', + ]) + + def assertGraphColoring(self, graph, colors): + self.assertGraphHasColors(graph, colors) + self.assertGraphColorLimit(graph) + for node in graph: + self.assertNodeUniqueColor(node) + + def assertGraphHasColors(self, graph, colors): + for node in graph: + msg = 'Node %r color %r not in %r' % (node.label, node.color, + colors) + self.assertIn(node.color, colors, msg=msg) + + def assertGraphColorLimit(self, graph): + max_degree = 0 + colors_found = set() + for node in graph: + degree = len(node.neighbors) + max_degree = max(degree, max_degree) + colors_found.add(node.color) + max_colors = max_degree + 1 + used_colors = len(colors_found) + msg = 'Used %d colors and expected %d at most' % (used_colors, + max_colors) + self.assertLessEqual(used_colors, max_colors, msg=msg) + + def assertNodeUniqueColor(self, node): + for adjacent in node.neighbors: + msg = 'Adjacent nodes %r and %r have the same color %r' % ( + node.label, + adjacent.label, + node.color, + ) + self.assertNotEqual(node.color, adjacent.color, msg=msg) + + def test_line_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + + node_a.neighbors.add(node_b) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_c.neighbors.add(node_b) + node_c.neighbors.add(node_d) + node_d.neighbors.add(node_c) + + graph = [node_a, node_b, node_c, node_d] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_separate_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + + node_a.neighbors.add(node_b) + node_b.neighbors.add(node_a) + node_c.neighbors.add(node_d) + node_d.neighbors.add(node_c) + + graph = [node_a, node_b, node_c, node_d] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_triangle_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + + node_a.neighbors.add(node_b) + node_a.neighbors.add(node_c) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_c.neighbors.add(node_a) + node_c.neighbors.add(node_b) + + graph = [node_a, node_b, node_c] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_envelope_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + node_e = GraphNode('e') + + node_a.neighbors.add(node_b) + node_a.neighbors.add(node_c) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_b.neighbors.add(node_d) + node_b.neighbors.add(node_e) + node_c.neighbors.add(node_a) + node_c.neighbors.add(node_b) + node_c.neighbors.add(node_d) + node_c.neighbors.add(node_e) + node_d.neighbors.add(node_b) + node_d.neighbors.add(node_c) + node_d.neighbors.add(node_e) + node_e.neighbors.add(node_b) + node_e.neighbors.add(node_c) + node_e.neighbors.add(node_d) + + graph = [node_a, node_b, node_c, node_d, node_e] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_loop_graph(self): + node_a = GraphNode('a') + node_a.neighbors.add(node_a) + graph = [node_a] + tampered_colors = list(self.colors) + with self.assertRaises(Exception): + color_graph(graph, tampered_colors) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py b/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py new file mode 100644 index 000000000000..0e7e97a20ca7 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py @@ -0,0 +1,39 @@ +from graphviz import Digraph +from collections import deque +from fixtures import weighted_graph + +# There are three ways to model a graph: +# 1. Edge list: [(Vertex, Vertex)] +# 2. Neighbors table: Map(Vertex, [Vertex]) +# 3. Adjacency matrix: [[Boolean]] +# +# The following graph is a neighbors table. + + +# to_graphviz :: Vertex -> Map(Vertex, [(Vertex, Weight)]) -> String +def to_graphviz(start, g): + """Compiles the graph into GraphViz.""" + d = Digraph() + q = deque() + seen = set() + + q.append(start) + + while q: + v = q.popleft() + if v in seen: + continue + d.node(v, label=v) + + for w, x in g[v]: + d.edge(v, x, label=str(w)) + q.append(x) + seen.add(v) + + return d.source + + +with open('/tmp/test.gv', 'w') as f: + src = to_graphviz('a', weighted_graph) + f.write(src) + print('/tmp/test.gv created!') diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py b/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py new file mode 100644 index 000000000000..889663e058da --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py @@ -0,0 +1,89 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# f :: [Int] -> Int +def highest_product_of_3(xs): + """Here we're greedily storing: + - current max + - largest product of two + - largest positive number + - second largest positive number + - largest negative number + """ + if len(xs) < 3: + raise Exception + + cm = None + ld = xs[0] * xs[1] + l2 = min(xs[0], xs[1]) + if xs[0] < 0 or xs[1] < 0: + ln = min(xs[0], xs[1]) + else: + ln = 1 + l = max(xs[0], xs[1]) + + for x in xs[2:]: + if not cm: + cm = max(x * ln * l, ld * x, x * l * l2) # beware + ld = max(ld, x * ln, x * l) + ln = min(ln, x) + l = max(l, x) + if x < l: + l2 = max(l2, x) + else: + cm = max(cm, x * ln * l, x * ld, x * l * l2) + ld = max(ld, x * ln, x * l) + ln = min(ln, x) + l = max(l, x) + if x < l: + l2 = max(l2, x) + + return cm + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_short_list(self): + actual = highest_product_of_3([1, 2, 3, 4]) + expected = 24 + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = highest_product_of_3([6, 1, 3, 5, 7, 8, 2]) + expected = 336 + self.assertEqual(actual, expected) + + def test_list_has_one_negative(self): + actual = highest_product_of_3([-5, 4, 8, 2, 3]) + expected = 96 + self.assertEqual(actual, expected) + + def test_list_has_two_negatives(self): + actual = highest_product_of_3([-10, 1, 3, 2, -10]) + expected = 300 + self.assertEqual(actual, expected) + + def test_list_is_all_negatives(self): + actual = highest_product_of_3([-5, -1, -3, -2]) + expected = -6 + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + highest_product_of_3([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + highest_product_of_3([1]) + + def test_error_with_two_numbers(self): + with self.assertRaises(Exception): + highest_product_of_3([1, 1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py b/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py new file mode 100644 index 000000000000..6e17baef3709 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py @@ -0,0 +1,35 @@ +# possible :: Int -> [Int] -> Bool +def possible(flight_duration, film_durations): + seeking = set() + + for x in film_durations: + if x in seeking: + return True + else: + seeking.add(flight_duration - x) + + return False + + +should = [ + (10, [1, 9, 8, 8, 8]), + (10, [1, 9]), + (10, [1, 9, 5, 5, 6]), + (1, [0.5, 0.5]), + (1, [0.5, 0.5]), +] + +for a, b in should: + print("Testing: %s %s" % (a, b)) + assert possible(a, b) + +shouldnt = [ + (10, [1, 10, 1, 2, 1, 12]), + (1, [0.25, 0.25, 0.25, 0.25]), + (5, [1, 2, 2]), +] +for a, b in shouldnt: + print("Testing: %s %s" % (a, b)) + assert not possible(a, b) + +print("Tests pass") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py b/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py new file mode 100644 index 000000000000..c72d19d4ed73 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py @@ -0,0 +1,38 @@ +import unittest +from math import floor + + +def knapify(xs, capacity=None): + assert capacity is not None + n = len(xs) + # For 0/1 Knapsack, we must use a table, since this will encode which values + # work for which items. This is cleaner than including a separate data + # structure to capture it. + maxes = [[0 for x in range(capacity + 1)] for x in range(n + 1)] + + # Build table maxes[][] in bottom up manner + for row in range(n + 1): + for col in range(capacity + 1): + if row == 0 or col == 0: + maxes[row][col] = 0 + elif xs[row - 1][0] <= col: + maxes[row][col] = max( + xs[row - 1][1] + maxes[row - 1][col - xs[row - 1][0]], + maxes[row - 1][col]) + else: + maxes[row][col] = maxes[row - 1][col] + + return maxes[-1][capacity] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_cake(self): + actual = knapify([(3, 10), (2, 15), (7, 2), (12, 20)], capacity=12) + expected = None + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py b/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py new file mode 100644 index 000000000000..8291e54533d5 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py @@ -0,0 +1,82 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def length(x): + if not x: + return 0 + else: + count = 1 + while x: + x = x.next + count += 1 + return count + + +def kth_to_last_node(k, x): + hops = length(x) - 1 + dest = hops - k + + if k == 0: + raise Exception("Our God doesn't support this kind of behavior.") + + if dest < 0: + raise Exception('Value k to high for list.') + + while dest > 0: + x = x.next + dest -= 1 + + return x + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_first_to_last_node(self): + actual = kth_to_last_node(1, self.first) + expected = self.fourth + self.assertEqual(actual, expected) + + def test_second_to_last_node(self): + actual = kth_to_last_node(2, self.first) + expected = self.third + self.assertEqual(actual, expected) + + def test_first_node(self): + actual = kth_to_last_node(4, self.first) + expected = self.first + self.assertEqual(actual, expected) + + def test_k_greater_than_linked_list_length(self): + with self.assertRaises(Exception): + kth_to_last_node(5, self.first) + + def test_k_is_zero(self): + with self.assertRaises(Exception): + kth_to_last_node(0, self.first) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py b/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py new file mode 100644 index 000000000000..aab9671eb6d3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py @@ -0,0 +1,107 @@ +import unittest + + +class Stack(object): + def __init__(self): + """Initialize an empty stack""" + self.items = [] + + def push(self, item): + """Push a new item onto the stack""" + self.items.append(item) + + def pop(self): + """Remove and return the last item""" + # If the stack is empty, return None + # (it would also be reasonable to throw an exception) + if not self.items: + return None + + return self.items.pop() + + def peek(self): + """Return the last item without removing it""" + if not self.items: + return None + return self.items[-1] + + +class MaxStack(object): + # Implement the push, pop, and get_max methods + def __init__(self): + self.m = Stack() + self.stack = Stack() + + def push(self, item): + if self.m.peek() is None: + self.m.push(item) + elif item >= self.m.peek(): + self.m.push(item) + self.stack.push(item) + + def pop(self): + x = self.stack.pop() + if x == self.m.peek(): + self.m.pop() + return x + + def get_max(self): + return self.m.peek() + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_stack_usage(self): + max_stack = MaxStack() + + max_stack.push(5) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + max_stack.push(4) + max_stack.push(7) + max_stack.push(7) + max_stack.push(8) + + actual = max_stack.get_max() + expected = 8 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 8 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 4 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py b/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py new file mode 100644 index 000000000000..75a4b993944c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py @@ -0,0 +1,88 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def contains_cycle(x): + if not x: + return False + elif not x.next: + return False + + a = x + b = x.next + + while b.next: + if a == b: + return True + + a = a.next + b = b.next.next + + return False + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def test_linked_list_with_no_cycle(self): + fourth = Test.LinkedListNode(4) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + result = contains_cycle(first) + self.assertFalse(result) + + def test_cycle_loops_to_beginning(self): + fourth = Test.LinkedListNode(4) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fourth.next = first + result = contains_cycle(first) + self.assertTrue(result) + + def test_cycle_loops_to_middle(self): + fifth = Test.LinkedListNode(5) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fifth.next = third + result = contains_cycle(first) + self.assertTrue(result) + + def test_two_node_cycle_at_end(self): + fifth = Test.LinkedListNode(5) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fifth.next = fourth + result = contains_cycle(first) + self.assertTrue(result) + + def test_empty_list(self): + result = contains_cycle(None) + self.assertFalse(result) + + def test_one_element_linked_list_no_cycle(self): + first = Test.LinkedListNode(1) + result = contains_cycle(first) + self.assertFalse(result) + + def test_one_element_linked_list_cycle(self): + first = Test.LinkedListNode(1) + first.next = first + result = contains_cycle(first) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/memo.py b/users/wpcarro/scratch/data_structures_and_algorithms/memo.py new file mode 100644 index 000000000000..44ea93e1bd49 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/memo.py @@ -0,0 +1,60 @@ +import time +import random +from heapq import heappush, heappop + + +class Memo(object): + def __init__(self, size=1): + """ + Create a key-value data-structure that will never exceed `size` + members. Memo evicts the least-recently-accessed elements from itself + before adding inserting new key-value pairs. + """ + if size <= 0: + raise Exception("We do not support an empty memo") + self.xs = {} + self.heap = [(0, None)] * size + + def contains(self, k): + """ + Return true if key `k` exists in the Memo. + """ + return k in self.xs + + def get(self, k): + """ + Return the memoized item at key `k`. + """ + # "touch" the element in the heap + return self.xs[k] + + def set(self, k, v): + """ + Memoize value `v` at key `k`. + """ + _, to_evict = heappop(self.heap) + if to_evict != None: + del self.xs[to_evict] + heappush(self.heap, (time.time(), k)) + self.xs[k] = v + + +memo = Memo(size=10) + + +def f(x): + """ + Compute some mysterious, expensive function. + """ + if memo.contains(x): + print("Hit.\t\tf({})".format(x)) + return memo.get(x) + else: + print("Computing...\tf({})".format(x)) + time.sleep(0.25) + res = random.randint(0, 10) + memo.set(x, res) + return res + + +[f(random.randint(0, 10)) for _ in range(10)] diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py b/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py new file mode 100644 index 000000000000..6dbe0fa0f3c3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py @@ -0,0 +1,28 @@ + + + +# merge :: [a] -> [a] -> [a] +# merge([], []): [] +# merge(xs, []): xs +# merge([], ys): ys +# merge(xs@[x|xs'], ys@[y|ys']) +# when y =< x: cons(y, merge(xs, ys')) +# when x < y: cons(x, merge(xs', ys)) +def merge(xs, ys): + if xs == [] and ys == []: + return [] + elif ys == []: + return xs + elif xs == []: + return ys + else: + x = xs[0] + y = ys[0] + + if y <= x: + return [y] + merge(xs, ys[1:]) + else: + return [x] + merge(xs[1:], ys) + +print(merge([3, 4, 6, 10, 11, 15], + [1, 5, 8, 12, 14, 19])) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py b/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py new file mode 100644 index 000000000000..4e3604d5bcca --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py @@ -0,0 +1,94 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# do_merge_ranges :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)] +def do_merge_ranges(prev, xs): + if len(xs) == 0: + return prev + elif len(xs) == 1: + return prev + xs + else: + a1, a2 = xs[0] + b1, b2 = xs[1] + rest = xs[2:] + if b1 <= a2: + return do_merge_ranges(prev, [(a1, max(a2, b2))] + rest) + else: + return do_merge_ranges(prev + [(a1, a2)], [(b1, b2)] + rest) + + +# merge_ranges :: [(Int, Int)] -> [(Int, Int)] +def merge_ranges(xs): + xs = xs[:] + xs.sort() + return do_merge_ranges([], xs) + + +# merge_ranges_b :: [(Int, Int)] -> [(Int, Int)] +def merge_ranges_b(xs): + fi = 0 + ci = 1 + result = [] + xs = xs[:] + xs.sort() + while ci < len(xs): + while ci < len(xs) and xs[ci][0] <= xs[fi][1]: + xs[fi] = xs[fi][0], max(xs[ci][1], xs[fi][1]) + ci += 1 + result.append(xs[fi]) + fi = ci + ci += 1 + if fi < len(xs): + result.append(xs[fi]) + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv new file mode 100644 index 000000000000..1e67c3954f5f --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv @@ -0,0 +1,11 @@ +strict graph { + Min -- {William, Jayden, Omar} + William -- {Min, Noam} + Jayden -- {Min, Amelia, Ren, Noam} + Adam -- {Amelia, Miguel, Sofia, Lucas} + Ren -- {Jayden, Omar} + Amelia -- {Jayden, Adam, Miguel} + Miguel -- {Amelia, Adam, Liam, Nathan} + Noam -- {Nathan, Jayden, William} + Omar -- {Ren, Min, Scott} +} diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py new file mode 100644 index 000000000000..c9d7d9d74151 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py @@ -0,0 +1,97 @@ +import unittest +from collections import deque + + +################################################################################ +# Solution +################################################################################ +# get_path :: G(V, E) -> V -> V -> Maybe([V]) +def get_path(g, src, dst): + q = deque() + result = None + seen = set() + q.append(([], src)) + + if src not in g or dst not in g: + raise Exception + + while q: + p, node = q.popleft() + + seen.add(node) + + if node == dst: + if not result: + result = p + [node] + elif len(p + [node]) < len(result): + result = p + [node] + else: + if node not in g: + raise Exception + for x in g.get(node): + if not x in seen: + q.append((p + [node], x)) + + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def setUp(self): + self.graph = { + 'a': ['b', 'c', 'd'], + 'b': ['a', 'd'], + 'c': ['a', 'e'], + 'd': ['a', 'b'], + 'e': ['c'], + 'f': ['g'], + 'g': ['f'], + } + + def test_two_hop_path_1(self): + actual = get_path(self.graph, 'a', 'e') + expected = ['a', 'c', 'e'] + self.assertEqual(actual, expected) + + def test_two_hop_path_2(self): + actual = get_path(self.graph, 'd', 'c') + expected = ['d', 'a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_1(self): + actual = get_path(self.graph, 'a', 'c') + expected = ['a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_2(self): + actual = get_path(self.graph, 'f', 'g') + expected = ['f', 'g'] + self.assertEqual(actual, expected) + + def test_one_hop_path_3(self): + actual = get_path(self.graph, 'g', 'f') + expected = ['g', 'f'] + self.assertEqual(actual, expected) + + def test_zero_hop_path(self): + actual = get_path(self.graph, 'a', 'a') + expected = ['a'] + self.assertEqual(actual, expected) + + def test_no_path(self): + actual = get_path(self.graph, 'a', 'f') + expected = None + self.assertEqual(actual, expected) + + def test_start_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'h', 'a') + + def test_end_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'a', 'h') + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/norman.py b/users/wpcarro/scratch/data_structures_and_algorithms/norman.py new file mode 100644 index 000000000000..379ba92abba8 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/norman.py @@ -0,0 +1,78 @@ + + + +# Write a function with the following type signature:L +# equal? :: String -> String -> Bool +# +# Determine equality between two inputs with backspace characters encoded as +# "<". + +################################################################################ +# Solution 1 +################################################################################ + +# from collections import deque + +# def equal(a, b): +# sa = deque() +# sb = deque() + +# for c in a: +# if c == '<': +# sa.pop() +# else: +# sa.append(c) + +# for c in b: +# if c == '<': +# sb.pop() +# else: +# sb.append(c) + +# return sa == sb + +################################################################################ +# Solution 2 +################################################################################ + +def handle_dels(num_dels, i, xs): + if i < 0: + return -1 + + while xs[i] == '<': + num_dels += 1 + i -= 1 + + while num_dels > 0 and xs[i] != '<': + num_dels -= 1 + i -= 1 + + if xs[i] == '<': + return handle_dels(num_dels, i, xs) + else: + return i + +def update_index(i, xs): + # TODO: Indexing into non-available parts of a string. + if xs[i] != '<' and xs[i - 1] != '<': + return i - 1 + + elif xs[i - 1] == '<': + return handle_dels(0, i - 1, xs) + +def equal(a, b): + ia = len(a) - 1 + ib = len(b) - 1 + + while ia >= 0 and ib >= 0: + if a[ia] != b[ib]: + return False + ia = update_index(ia, a) + ib = update_index(ib, b) + + if ia != 0: + return update_index(ia, a) <= -1 + if ib != 0: + return update_index(ib, b) <= -1 + + return True diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py b/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py new file mode 100644 index 000000000000..cdb2846ea338 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py @@ -0,0 +1,59 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def fib(n): + """This should be accomplishable in O(1) space.""" + if n in {0, 1}: + return n + a = 0 # i = 0 + b = 1 # i = 1 + for x in range(2, n + 1): + result = a + b + a = b + b = result + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_zeroth_fibonacci(self): + actual = fib(0) + expected = 0 + self.assertEqual(actual, expected) + + def test_first_fibonacci(self): + actual = fib(1) + expected = 1 + self.assertEqual(actual, expected) + + def test_second_fibonacci(self): + actual = fib(2) + expected = 1 + self.assertEqual(actual, expected) + + def test_third_fibonacci(self): + actual = fib(3) + expected = 2 + self.assertEqual(actual, expected) + + def test_fifth_fibonacci(self): + actual = fib(5) + expected = 5 + self.assertEqual(actual, expected) + + def test_tenth_fibonacci(self): + actual = fib(10) + expected = 55 + self.assertEqual(actual, expected) + + def test_negative_fibonacci(self): + with self.assertRaises(Exception): + fib(-1) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py b/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py new file mode 100644 index 000000000000..af13239941d0 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py @@ -0,0 +1,49 @@ +from random import choice +from math import floor + +# Applying Chapter 1 from "Algorithms to Live By", which describes optimal +# stopping problems. Technically this simulation is invalid because the +# `candidates` function takes a lower bound and an upper bound, which allows us +# to know the cardinal number of an individual candidates. The "look then leap" +# algorithm is ideal for no-information games - i.e. games when upper and lower +# bounds aren't known. The `look_then_leap/1` function is ignorant of this +# information, so it behaves as if in a no-information game. Strangely enough, +# this algorithm will pick the best candidate 37% of the time. +# +# Chapter 1 describes two algorithms: +# 1. Look-then-leap: ordinal numbers - i.e. no-information games. Look-then-leap +# finds the best candidate 37% of the time. +# 2. Threshold: cardinal numbers - i.e. where upper and lower bounds are +# known. The Threshold algorithm finds the best candidate ~55% of the time. +# +# All of this and more can be studied as "optimal stopping theory". This applies +# to finding a spouse, parking a car, picking an apartment in a city, and more. + + +# candidates :: Int -> Int -> Int -> [Int] +def candidates(lb, ub, ct): + xs = list(range(lb, ub + 1)) + return [choice(xs) for _ in range(ct)] + + +# look_then_leap :: [Integer] -> Integer +def look_then_leap(candidates): + best = candidates[0] + seen_ct = 1 + ignore_ct = floor(len(candidates) * 0.37) + for x in candidates[1:]: + if ignore_ct > 0: + ignore_ct -= 1 + best = max(best, x) + else: + if x > best: + print('Choosing the {} candidate.'.format(seen_ct)) + return x + seen_ct += 1 + print('You may have waited too long.') + return candidates[-1] + + +candidates = candidates(1, 100, 100) +print(candidates) +print(look_then_leap(candidates)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py b/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py new file mode 100644 index 000000000000..0eb389c26bb9 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py @@ -0,0 +1,83 @@ +import unittest + + +################################################################################ +# Answer +################################################################################ +class Node(object): + def __init__(self, value, children=set()): + self.value = value + self.children = children + + +# treeify :: Char -> Set(Char) -> Node(Char) +def treeify(x, xs): + return Node(x, [treeify(c, xs - {c}) for c in xs]) + + +# dft :: Node(Char) -> [String] +def dft(node): + result = [] + s = [] + + s.append(('', node)) + + while s: + p, n = s.pop() + p += str(n.value) + + if not n.children: + result.append(p) + else: + for c in n.children: + s.append((p, c)) + + return result + + +# main :: String -> Set(String) +def get_permutations(xs): + if xs == '': + return set(['']) + + ys = set(xs) + trees = [] + + for y in ys: + trees.append(treeify(y, ys - {y})) + + result = set() + + for t in trees: + for d in dft(t): + result.add(d) + + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py b/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py new file mode 100644 index 000000000000..0a2136a408f2 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py @@ -0,0 +1,49 @@ +from collections import Counter +import unittest + + +################################################################################ +# Impl +################################################################################ +# palindromifiable :: String -> Boolean +def has_palindrome_permutation(x): + bag = Counter(x) + odd_entries_ct = 0 + + for _, y in bag.items(): + if y % 2 != 0: + odd_entries_ct += 1 + + return odd_entries_ct in {0, 1} + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcbcd') + self.assertTrue(result) + + def test_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabccbdd') + self.assertTrue(result) + + def test_no_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcd') + self.assertFalse(result) + + def test_no_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabbcd') + self.assertFalse(result) + + def test_empty_string(self): + result = has_palindrome_permutation('') + self.assertTrue(result) + + def test_one_character_string(self): + result = has_palindrome_permutation('a') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py b/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py new file mode 100644 index 000000000000..fc2c1ef7eebc --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py @@ -0,0 +1,55 @@ +class Node(object): + # ctor :: a -> [a] -> Node(a) + def __init__(self, value, children=[]): + self.value = value + self.children = children + + +# is_leaf :: Node(a) -> Boolean +def is_leaf(node): + return len(node.children) == 0 + + +# enumerate :: Node(a) -> Set(List(a)) +def enumerate(node): + current = [] + result = [] + q = [] + + q.append(node) + + while q: + x = q.pop() + print(x.value) + + for c in x.children: + q.append(c) + + current.append(x.value) + print(current) + + if is_leaf(x): + result.append(current) + print("Reseting current") + current = [] + + return result + + +node = Node('root', [ + Node('a', [ + Node('b', [Node('c')]), + Node('c', [Node('b')]), + ]), + Node('b', [ + Node('a', [Node('c')]), + Node('c', [Node('a')]), + ]), + Node('c', [ + Node('a', [Node('b')]), + Node('b', [Node('a')]), + ]) +]) + +print('----------') +print(enumerate(node)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/plot.py b/users/wpcarro/scratch/data_structures_and_algorithms/plot.py new file mode 100644 index 000000000000..5601891a0d9b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/plot.py @@ -0,0 +1,9 @@ +import numpy as np +import matplotlib.pyplot as plt + +rng = np.random.RandomState(10) # deterministic random data +a = np.hstack((rng.normal(size=1000), rng.normal(loc=5, scale=2, size=1000))) +_ = plt.hist(a, bins='auto') # arguments are passed to np.histogram +plt.title("Histogram with 'auto' bins") +Text(0.5, 1.0, "Histogram with 'auto' bins") +plt.show() diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py b/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py new file mode 100644 index 000000000000..d05e82d42b02 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py @@ -0,0 +1,68 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# f :: [Int] -> [Int] +def get_products_of_all_ints_except_at_index(xs): + if len(xs) in {0, 1}: + raise Exception + + ct = len(xs) + lefts = [1] * ct + rights = [1] * ct + result = [] + + for i in range(1, ct): + lefts[i] = lefts[i - 1] * xs[i - 1] + for i in range(ct - 2, -1, -1): + rights[i] = rights[i + 1] * xs[i + 1] + + return [lefts[i] * rights[i] for i in range(ct)] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_small_list(self): + actual = get_products_of_all_ints_except_at_index([1, 2, 3]) + expected = [6, 3, 2] + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = get_products_of_all_ints_except_at_index([8, 2, 4, 3, 1, 5]) + expected = [120, 480, 240, 320, 960, 192] + self.assertEqual(actual, expected) + + def test_list_has_one_zero(self): + actual = get_products_of_all_ints_except_at_index([6, 2, 0, 3]) + expected = [0, 0, 36, 0] + self.assertEqual(actual, expected) + + def test_list_has_two_zeros(self): + actual = get_products_of_all_ints_except_at_index([4, 0, 9, 1, 0]) + expected = [0, 0, 0, 0, 0] + self.assertEqual(actual, expected) + + def test_one_negative_number(self): + actual = get_products_of_all_ints_except_at_index([-3, 8, 4]) + expected = [32, -12, -24] + self.assertEqual(actual, expected) + + def test_all_negative_numbers(self): + actual = get_products_of_all_ints_except_at_index([-7, -1, -4, -2]) + expected = [-8, -56, -14, -28] + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py b/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py new file mode 100644 index 000000000000..63da08ebf79a --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py @@ -0,0 +1,66 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +class QueueTwoStacks(object): + def __init__(self): + self.a = [] + self.b = [] + + def enqueue(self, x): + self.a.append(x) + + def dequeue(self): + if self.b: + return self.b.pop() + else: + while self.a: + self.b.append(self.a.pop()) + return self.dequeue() + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_basic_queue_operations(self): + queue = QueueTwoStacks() + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(3) + actual = queue.dequeue() + expected = 1 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 2 + self.assertEqual(actual, expected) + queue.enqueue(4) + actual = queue.dequeue() + expected = 3 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 4 + self.assertEqual(actual, expected) + + def test_error_when_dequeue_from_new_queue(self): + queue = QueueTwoStacks() + with self.assertRaises(Exception): + queue.dequeue() + + def test_error_when_dequeue_from_empty_queue(self): + queue = QueueTwoStacks() + queue.enqueue(1) + queue.enqueue(2) + actual = queue.dequeue() + expected = 1 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 2 + self.assertEqual(actual, expected) + with self.assertRaises(Exception): + queue.dequeue() + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py b/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py new file mode 100644 index 000000000000..47c0f53979c6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py @@ -0,0 +1,246 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# bottom :: Rectangle -> Int +def bottom(x): + return x.get('bottom_y') + + +# top :: Rectangle -> Int +def top(x): + return bottom(x) + x.get('height') + + +# left :: Rectangle -> Int +def left(x): + return x.get('left_x') + + +# right :: Rectangle -> Int +def right(x): + return left(x) + x.get('width') + + +# sort_highest :: Rectangle -> Rectangle -> (Rectangle, Rectangle) +def sort_highest(x, y): + if top(x) >= top(y): + return x, y + else: + return y, x + + +# sort_leftmost :: Rectangle -> Rectangle -> (Rectangle, Rectangle) +def sort_leftmost(x, y): + if left(x) <= left(y): + return x, y + else: + return y, x + + +# rectify :: Int -> Int -> Int -> Int -> Rectify +def rectify(top=None, bottom=None, left=None, right=None): + assert top >= bottom + assert left <= right + return { + 'left_x': left, + 'bottom_y': bottom, + 'width': right - left, + 'height': top - bottom, + } + + +# empty_rect :: Rectangle +def empty_rect(): + return { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + + +# find_rectangular_overlap :: Rectangle -> Rectangle -> Maybe(Rectangle) +def find_rectangular_overlap(x, y): + ha, hb = sort_highest(x, y) + la, lb = sort_leftmost(x, y) + + if bottom(hb) <= top(hb) <= bottom(ha) <= top(ha): + return empty_rect() + + if left(la) <= right(la) <= left(lb) <= right(lb): + return empty_rect() + + # We should have an intersection here. + verts = [bottom(ha), top(ha), bottom(hb), top(hb)] + verts.sort() + horzs = [left(la), right(la), left(lb), right(lb)] + horzs.sort() + return rectify(top=verts[2], + bottom=verts[1], + left=horzs[1], + right=horzs[2]) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_overlap_along_both_axes(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 6, + 'height': 3, + } + rect2 = { + 'left_x': 5, + 'bottom_y': 2, + 'width': 3, + 'height': 6, + } + expected = { + 'left_x': 5, + 'bottom_y': 2, + 'width': 2, + 'height': 2, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_one_rectangle_inside_another(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 6, + 'height': 6, + } + rect2 = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_both_rectangles_the_same(self): + rect1 = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + rect2 = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + expected = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_on_horizontal_edge(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 2, + 'width': 3, + 'height': 4, + } + rect2 = { + 'left_x': 2, + 'bottom_y': 6, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_on_vertical_edge(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 2, + 'width': 3, + 'height': 4, + } + rect2 = { + 'left_x': 4, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_at_a_corner(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 2, + 'height': 2, + } + rect2 = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_no_overlap(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 2, + 'height': 2, + } + rect2 = { + 'left_x': 4, + 'bottom_y': 6, + 'width': 3, + 'height': 6, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py b/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py new file mode 100644 index 000000000000..70461ddf5dac --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py @@ -0,0 +1,37 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +# get_permutations :: String -> Set(String) +def get_permutations(string): + pass + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py new file mode 100644 index 000000000000..b7396b20ce3f --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py @@ -0,0 +1,79 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# reverse :: List(a) -> List(a) +def reverse(node): + curr = node + prev = None + while curr: + nxt = curr.next + curr.next = prev + prev = curr + curr = nxt + # Make sure to understand the spec! Debugging takes time. Rewriting takes + # time. + return prev + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def test_short_linked_list(self): + second = Test.LinkedListNode(2) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [2, 1] + self.assertEqual(actual, expected) + + def test_long_linked_list(self): + sixth = Test.LinkedListNode(6) + fifth = Test.LinkedListNode(5, sixth) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [6, 5, 4, 3, 2, 1] + self.assertEqual(actual, expected) + + def test_one_element_linked_list(self): + first = Test.LinkedListNode(1) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [1] + self.assertEqual(actual, expected) + + def test_empty_linked_list(self): + result = reverse(None) + self.assertIsNone(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py new file mode 100644 index 000000000000..5df12ebabdc7 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py @@ -0,0 +1,181 @@ +from collections import deque +import unittest + +################################################################################ +# Solution +################################################################################ + + +def rev(xs, i, j): + """Reverse xs in place from [i, j]""" + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + + +def rotate(xs, n, i=None, j=None): + """Mutably rotates list, xs, n times. Positive n values rotate right while + negative n values rotate left. Rotate within window [i, j].""" + i = i or 0 + j = j or len(xs) - 1 + ct = j - i + + if n < 0: + n = abs(n) + p = i + n - 1 + rev(xs, i, p) + rev(xs, p + 1, j) + rev(xs, i, j) + else: + p = j - (n - 1) + rev(xs, p, j) + rev(xs, i, p - 1) + rev(xs, i, j) + return xs + + +def rev_words(xs, i, j): + if j + 1 == len(xs): + return 0 + + while j + 1 < len(xs): + while j + 1 < len(xs) and xs[j + 1] != ' ': + j += 1 + + rev(xs, i, j) + j += 2 + i = j + + return 0 + + +def reverse_words(xs): + # first reverse everything + rev(xs, 0, len(xs) - 1) + return rev_words(xs, 0, 0) + + +def reverse_words_bak(xs, i=None, j=None): + i = i or 0 + j = j or len(xs) - 1 + w0, w1 = [], [] + + if i >= j: + return 0 + + pi = i + while pi < len(xs) and xs[pi] != ' ': + w0.append(xs[pi]) + pi += 1 + + if pi == len(xs): + return 0 + + pj = j + while xs[pj] != ' ': + w1.append(xs[pj]) + pj -= 1 + + d = len(w0) - len(w1) + + rotate(xs, -1 * d, i, j) + + for k in range(len(w1)): + xs[i + k] = w1[len(w1) - 1 - k] + + for k in range(len(w0)): + xs[j - k] = w0[len(w0) - 1 - k] + + while i != j and xs[i] != ' ' and xs[j] != ' ': + i += 1 + j -= 1 + + if i == j: + return 0 + + elif xs[i] == ' ': + while j > 0 and xs[j] != ' ': + j -= 1 + if j == 0: + return 0 + elif xs[j] == ' ': + while i < len(xs) and xs[i] != ' ': + i += 1 + if i == len(xs): + return 0 + return reverse_words(xs, i + 1, j - 1) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_rev(self): + xs = [1, 2, 3, 4, 5] + rev(xs, 0, len(xs) - 1) + self.assertEqual(xs, [5, 4, 3, 2, 1]) + + def test_rotate(self): + ys = [1, 2, 3, 4, 5] + xs = ys[:] + self.assertEqual(rotate(xs, 1, 1, 3), [1, 4, 2, 3, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, -1, 1, 3), [1, 3, 4, 2, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 1), [5, 1, 2, 3, 4]) + xs = ys[:] + self.assertEqual(rotate(xs, -1), [2, 3, 4, 5, 1]) + xs = ys[:] + self.assertEqual(rotate(xs, -2), [3, 4, 5, 1, 2]) + xs = ys[:] + self.assertEqual(rotate(xs, -5), [1, 2, 3, 4, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 5), [1, 2, 3, 4, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 3), [3, 4, 5, 1, 2]) + + def test_one_word(self): + message = list('vault') + reverse_words(message) + expected = list('vault') + self.assertEqual(message, expected) + + def test_two_words(self): + message = list('thief cake') + reverse_words(message) + expected = list('cake thief') + self.assertEqual(message, expected) + + def test_three_words(self): + message = list('one another get') + reverse_words(message) + expected = list('get another one') + self.assertEqual(message, expected) + + def test_multiple_words_same_length(self): + message = list('rat the ate cat the') + reverse_words(message) + expected = list('the cat ate the rat') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('at rat house') + reverse_words(message) + expected = list('house rat at') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('yummy is cake bundt chocolate') + reverse_words(message) + expected = list('chocolate bundt cake is yummy') + self.assertEqual(message, expected) + + def test_empty_string(self): + message = list('') + reverse_words(message) + expected = list('') + self.assertEqual(message, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py b/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py new file mode 100644 index 000000000000..bc167d975a7b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py @@ -0,0 +1,179 @@ +import unittest +from collections import deque + + +################################################################################ +# Implementation +################################################################################ +def is_leaf(node): + return node.left is None and node.right is None + + +def find_largest(node): + current = node + while current.right is not None: + current = current.right + return current.value + + +def find_second_largest(node): + history = deque() + current = node + + while current.right: + history.append(current) + current = current.right + + if current.left: + return find_largest(current.left) + elif history: + return history.pop().value + else: + raise TypeError + + +def find_second_largest_backup(node): + history = deque() + current = node + + # traverse -> largest + while current.right: + history.append(current) + current = current.right + + if current.left: + return find_largest(current.left) + elif history: + return history.pop().value + else: + raise ArgumentError + + +# Write a iterative version to avoid consuming memory with the call stack. +# Commenting out the recursive code for now. +def find_second_largest_backup(node): + if node.left is None and node.right is None: + raise ArgumentError + + elif node.right is None and is_leaf(node.left): + return node.left.value + + # recursion + # elif node.right is None: + # return find_largest(node.left) + + # iterative version + elif node.right is None: + current = node.left + while current.right is not None: + current = current.right + return current.value + + # recursion + # TODO: Remove recursion from here. + elif not is_leaf(node.right): + return find_second_largest(node.right) + + # could do an else here, but let's be more assertive. + elif is_leaf(node.right): + return node.value + + else: + raise ArgumentError + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + actual = find_second_largest(tree) + expected = 70 + self.assertEqual(actual, expected) + + def test_largest_has_a_left_child(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + actual = find_second_largest(tree) + expected = 60 + self.assertEqual(actual, expected) + + def test_largest_has_a_left_subtree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right_left = right.insert_left(60) + right_left_left = right_left.insert_left(55) + right_left.insert_right(65) + right_left_left.insert_right(58) + actual = find_second_largest(tree) + expected = 65 + self.assertEqual(actual, expected) + + def test_second_largest_is_root_node(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + actual = find_second_largest(tree) + expected = 50 + self.assertEqual(actual, expected) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + actual = find_second_largest(tree) + expected = 40 + self.assertEqual(actual, expected) + + def test_ascending_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(60) + right_right = right.insert_right(70) + right_right.insert_right(80) + actual = find_second_largest(tree) + expected = 70 + self.assertEqual(actual, expected) + + def test_error_when_tree_has_one_node(self): + tree = Test.BinaryTreeNode(50) + with self.assertRaises(Exception): + find_second_largest(tree) + + def test_error_when_tree_is_empty(self): + with self.assertRaises(Exception): + find_second_largest(None) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py b/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py new file mode 100644 index 000000000000..e08ea66b8f50 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py @@ -0,0 +1,94 @@ +from heapq import heappush, heappop +from collections import deque +from fixtures import weighted_graph, expanded_weights_graph + +# UnweightedGraph(a) :: Map(a, Set(a)) +# WeightedGraph(a) :: Map(a, Set(a)) + + +# shortest_path_dijkstra :: Vertex -> Vertex -> WeightedGraph(Vertex) +def shortest_path_dijkstra(a, b, g): + q = [] + seen = set() + + heappush(q, (0, a, [a])) + + while q: + w0, v0, path = heappop(q) + if v0 in seen: + continue + elif v0 == b: + return w0, path + for w1, v1 in g.get(v0): + heappush(q, (w0 + w1, v1, path + [v1])) + seen.add(v0) + return 'weighted', 'pizza' + + +# expand_edge :: Vertex -> (Weight, Vertex) -> Map(Vertex, [Vertex]) +def expand_edge(v0, wv): + w, v1 = wv + assert w > 1 + + result = {v0: ['{}-{}'.format(v1, 1)]} + for x in range(w - 2): + result['{}-{}'.format(v1, x + 1)] = ['{}-{}'.format(v1, x + 2)] + result['{}-{}'.format(v1, w - 1)] = [v1] + + return result + + +# expand_weights :: Vertex -> WeightedGraph(Vertex) -> UnweightedGraph(Vertex) +def expand_weights(v, g): + result = {} + q = deque() + seen = set() + + q.append(v) + while q: + v = d.popleft() + if v in seen: + continue + x = expand_edge(v, g.get) + for w, v1 in g.get(v): + if w > 1: + ws = expand_edge(v, (w, v1)) + result = {**result, **ws} + q.append(v) + pass + + +# shortest_path_inject :: Vertex -> Vertex -> WeightedGraph(Vertex) +def shortest_path_inject(a, b, g): + q = deque() + seen = set() + + q.append((a, [a])) + + while q: + v0, path = q.popleft() + if v0 == 'dummy': + continue + elif v0 in seen: + continue + elif v0 == b: + return len(path), path + for _, v1 in g.get(v0): + q.append((v1, path + [v1])) + seen.add(v0) + continue + + return None, None + + +print(expand_edge('a', (4, 'b'))) +print(expand_edge('a', (5, 'e'))) +assert expand_weights('a', weighted_graph) == expanded_weights_graph +# a = 'a' +# b = 'd' +# w, x = shortest_path_dijkstra(a, b, weighted_graph) +# w1, x1 = shortest_path_inject(a, b, weighted_graph) +# print("[dijkstra] Shortest path from {} to {} is {} with weight {}".format( +# a, b, x, w)) +# print("[injection] Shortest path from {} to {} is {} with weight {}".format( +# a, b, x1, w1)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py b/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py new file mode 100644 index 000000000000..bdfbad24263c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py @@ -0,0 +1,34 @@ +import random + + +def get_random(floor, ceiling): + return random.randrange(floor, ceiling + 1) + + +# shuffle_in_place :: [a] -> IO () +def shuffle_in_place(xs): + """Fisher-Yates algorithm. Notice that shuffling here is the same as + selecting a random permutation of the input set, `xs`.""" + n = len(xs) - 1 + for i in range(len(xs)): + r = get_random(i, n) + xs[i], xs[r] = xs[r], xs[i] + return xs + + +# shuffle :: [a] -> [a] +def shuffle_not_in_place(xs): + result = [] + + while xs: + i = get_random(0, len(xs) - 1) + x = xs.pop(i) + result.append(x) + + return result + + +xs = [x for x in range(9)] +print(xs) +# print(shuffle_not_in_place(xs)) +print(shuffle_in_place(xs[:])) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py b/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py new file mode 100644 index 000000000000..8b4cdac1c271 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py @@ -0,0 +1,22 @@ + +# swap :: Int -> Int -> [Char] -> IO () +def swap(ia, iz, xs): + # handle swap when ia == iz + assert ia <= iz + xs[ia], xs[iz] = xs[iz], xs[ia] + + +# reverse :: [Char] -> IO () +def reverse(xs): + ia = 0 + iz = len(xs) - 1 + + while ia <= iz: + swap(ia, iz, xs) + ia += 1 + iz -= 1 + +x = list("superduperpooper") +reverse(x) +print(x) +print("Tests pass") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py b/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py new file mode 100644 index 000000000000..6b042182f01c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py @@ -0,0 +1,84 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +class TempTracker(object): + def __init__(self): + # min / max + self.min = None + self.max = None + # mean + self.sum = 0 + self.num = 0 + # mode + self.nums = [0] * 111 + self.mode_num = 0 + self.mode = None + + def insert(self, x): + # min / max + if not self.min or x < self.min: + self.min = x + if not self.max or x > self.max: + self.max = x + # mean + self.sum += x + self.num += 1 + # mode + self.nums[x] += 1 + if self.nums[x] >= self.mode_num: + self.mode_num = self.nums[x] + self.mode = x + + def get_max(self): + return self.max + + def get_min(self): + return self.min + + def get_mean(self): + return self.sum / self.num + + def get_mode(self): + return self.mode + + +# Tests + + +class Test(unittest.TestCase): + def test_tracker_usage(self): + tracker = TempTracker() + + tracker.insert(50) + msg = 'failed on first temp recorded' + self.assertEqual(tracker.get_max(), 50, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 50.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 50, msg='mode ' + msg) + + tracker.insert(80) + msg = 'failed on higher temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 65.0, msg='mean ' + msg) + self.assertIn(tracker.get_mode(), [50, 80], msg='mode ' + msg) + + tracker.insert(80) + msg = 'failed on third temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 70.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 80, msg='mode ' + msg) + + tracker.insert(30) + msg = 'failed on lower temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 30, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 60.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 80, msg='mode ' + msg) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/test.txt b/users/wpcarro/scratch/data_structures_and_algorithms/test.txt new file mode 100644 index 000000000000..ce013625030b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/test.txt @@ -0,0 +1 @@ +hello diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py b/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py new file mode 100644 index 000000000000..8e7b073dd8bd --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py @@ -0,0 +1,25 @@ +from collections import deque + +# list: +# array: +# vector: +# bit-{array,vector}: + + +def sort(xs, highest): + v = [0] * (highest + 1) + result = deque() + + for x in xs: + v[x] += 1 + + for i, x in enumerate(v): + if x > 0: + result.appendleft(i) + + return list(result) + + +assert sort([37, 89, 41, 100, 65, 91, 53], + 100) == [100, 91, 89, 65, 53, 41, 37] +print("Tests pass!") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py b/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py new file mode 100644 index 000000000000..fe295b0279ff --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py @@ -0,0 +1,31 @@ +from fixtures import unweighted_digraph +from collections import deque + +# vertices_no_in_edges :: UnweightedDigraph -> Set(Vertex) +def vertices_no_in_edges(g): + """Return the vertices in graph `g` with no in-edges.""" + result = set() + vertices = set(g.keys()) + for neighbors in g.values(): + result = result.union(neighbors) + return vertices ^ result + +# topo_sort :: UnweightedDigraph -> List(Vertex) +def topo_sort(g): + q = deque() + seen = set() + result = [] + for x in vertices_no_in_edges(g): + q.append(x) + while q: + vertex = q.popleft() + if vertex in seen: + continue + result.append(vertex) + neighbors = g.get(vertex) + for x in g.get(vertex): + q.append(x) + seen.add(vertex) + return result + +print(topo_sort(unweighted_digraph)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py b/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py new file mode 100644 index 000000000000..45621990ecf9 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py @@ -0,0 +1,38 @@ +class Node(object): + def __init__(self, value, children=[]): + self.value = value + self.children = children + + +################################################################################ +# Solution +################################################################################ +def trip_time(node): + s = [] + result = 0 + s.append((node.value, node)) + while s: + p, node = s.pop() + if not node.children: + result = max(result, p) + for x in node.children: + s.append((p + x.value, x)) + return result + + +################################################################################ +# Tests +################################################################################ +tree = Node( + 0, + children=[ + Node(5, children=[Node(6)]), + Node(2, children=[ + Node(6), + Node(10), + ]), + Node(3, children=[Node(2, children=[Node(11)])]), + ]) + +assert trip_time(tree) == 16 +print("Tests pass!") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py b/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py new file mode 100644 index 000000000000..e9a4f0eb24d0 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py @@ -0,0 +1,33 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# find_repeat :: [Int] -> Int +def find_repeat(xs): + n = len(xs) - 1 + return sum(xs) - ((n**2 + n) / 2) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_short_list(self): + actual = find_repeat([1, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([4, 1, 3, 4, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([1, 5, 9, 7, 2, 6, 3, 8, 2, 4]) + expected = 2 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py b/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py new file mode 100644 index 000000000000..7fc174a2a9f3 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py @@ -0,0 +1,123 @@ +import unittest +from collections import deque + + +def is_balanced(node): + q, seen, ds = deque(), set(), set() + q.append((0, node)) + while q: + d, node = q.popleft() + l, r = node.left, node.right + seen.add(node) + if not l and not r: + if d not in ds and len(ds) == 2: + return False + else: + ds.add(d) + if l and l not in seen: + q.append((d + 1, l)) + if r and r not in seen: + q.append((d + 1, r)) + return max(ds) - min(ds) <= 1 + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/dijkstra.py b/users/wpcarro/scratch/deepmind/part_one/dijkstra.py new file mode 100644 index 000000000000..6975dbe4d1d6 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/dijkstra.py @@ -0,0 +1,26 @@ +# Doing a practice implementation of Dijkstra's algorithm: a priority-first +# search. +from heapq import heappush, heappop + + +class Node(object): + def __init__(self, value, children): + self.value = value + self.children = children + + +def shortest_path(a, b): + """Return the shortest path from `a` to `b`.""" + q = [] + seen = set() + heappush((a.value, a, [a]), q) + + while q: + d, node, path = heappop(q) + if node == b: + return path + seen.add(node) + for child in node.children: + if child not in seen: + heappush((d + child.value, child, path + [child]), q) + raise Exception("Path between nodes A and B does not exist.") diff --git a/users/wpcarro/scratch/deepmind/part_one/efficiency.org b/users/wpcarro/scratch/deepmind/part_one/efficiency.org new file mode 100644 index 000000000000..89a45c52ad8a --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/efficiency.org @@ -0,0 +1,6 @@ +* Sorting +** Merge: O(n*log(n)) +** Heap: O(n*log(n)) +** Insertion: O(n^2) +** Quick: O(n^2) +** Bubble: O(n^2) diff --git a/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py b/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py new file mode 100644 index 000000000000..5c21d5167ce9 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py @@ -0,0 +1,55 @@ +import unittest +from math import floor + + +def midpoint(a, b): + return a + floor((b - a) / 2) + + +def do_find_rotation_point(a, b, xs): + i = midpoint(a, b) + count = b - a + 1 + + if count == 2: + if xs[a] > xs[b]: + return b + else: + return -1 + + if i in {a, b}: + return i + + if xs[a] < xs[i]: + return do_find_rotation_point(i, b, xs) + else: + return do_find_rotation_point(a, i, xs) + + +def find_rotation_point(xs): + return do_find_rotation_point(0, len(xs) - 1, xs) + + +# Tests +class Test(unittest.TestCase): + def test_small_list(self): + actual = find_rotation_point(['cape', 'cake']) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_rotation_point( + ['grape', 'orange', 'plum', 'radish', 'apple']) + expected = 4 + self.assertEqual(actual, expected) + + def test_large_list(self): + actual = find_rotation_point([ + 'ptolemaic', 'retrograde', 'supplant', 'undulate', 'xenoepist', + 'asymptote', 'babka', 'banoffee', 'engender', 'karpatka', + 'othellolagkage' + ]) + expected = 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py b/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py new file mode 100644 index 000000000000..2116b27b0b97 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py @@ -0,0 +1,51 @@ +import unittest + + +def can_two_movies_fill_flight(xs, t): + seeking = set() + for x in xs: + if x in seeking: + return True + else: + seeking.add(t - x) + return False + + +# Tests + + +class Test(unittest.TestCase): + def test_short_flight(self): + result = can_two_movies_fill_flight([2, 4], 1) + self.assertFalse(result) + + def test_long_flight(self): + result = can_two_movies_fill_flight([2, 4], 6) + self.assertTrue(result) + + def test_one_movie_half_flight_length(self): + result = can_two_movies_fill_flight([3, 8], 6) + self.assertFalse(result) + + def test_two_movies_half_flight_length(self): + result = can_two_movies_fill_flight([3, 8, 3], 6) + self.assertTrue(result) + + def test_lots_of_possible_pairs(self): + result = can_two_movies_fill_flight([1, 2, 3, 4, 5, 6], 7) + self.assertTrue(result) + + def test_not_using_first_movie(self): + result = can_two_movies_fill_flight([4, 3, 2], 5) + self.assertTrue(result) + + def test_only_one_movie(self): + result = can_two_movies_fill_flight([6], 6) + self.assertFalse(result) + + def test_no_movies(self): + result = can_two_movies_fill_flight([], 2) + self.assertFalse(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py b/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py new file mode 100644 index 000000000000..5335e419f7ec --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py @@ -0,0 +1,64 @@ +import unittest + + +def kth_to_last_node(k, x): + a, b = x, x + + if k == 0: + raise Exception('Value of 0 for k is not supported') + + for _ in range(k - 1): + if not a.next: + raise Exception('Value of {} for k is too large'.format(k)) + a = a.next + + while a.next: + a, b = a.next, b.next + return b + + +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_first_to_last_node(self): + actual = kth_to_last_node(1, self.first) + expected = self.fourth + self.assertEqual(actual, expected) + + def test_second_to_last_node(self): + actual = kth_to_last_node(2, self.first) + expected = self.third + self.assertEqual(actual, expected) + + def test_first_node(self): + actual = kth_to_last_node(4, self.first) + expected = self.first + self.assertEqual(actual, expected) + + def test_k_greater_than_linked_list_length(self): + with self.assertRaises(Exception): + kth_to_last_node(5, self.first) + + def test_k_is_zero(self): + with self.assertRaises(Exception): + kth_to_last_node(0, self.first) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py b/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py new file mode 100644 index 000000000000..23b40793b8f1 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py @@ -0,0 +1,59 @@ +import unittest + + +def merge_ranges(xs): + xs.sort() + result = [xs[0]] + for curr in xs[1:]: + a, z = result[-1] + if z >= curr[0]: + result[-1] = (a, max(z, curr[1])) + else: + result.append(curr) + return result + + +# Tests +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py b/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py new file mode 100644 index 000000000000..f50db2838707 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py @@ -0,0 +1,56 @@ +import unittest +from itertools import permutations + + +class Node(object): + def __init__(self, x): + self.value = x + self.children = [] + + +def make_tree(c, xs): + root = Node(c) + for x in xs: + root.children.append(make_tree(x, xs - {x})) + return root + + +def get_permutations(xs): + xs = set(xs) + root = make_tree("", xs) + q, perms = [], set() + q.append(("", root)) + while q: + c, node = q.pop() + if not node.children: + perms.add(c) + else: + for child in node.children: + q.append((c + child.value, child)) + return perms + + +# Tests +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py b/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py new file mode 100644 index 000000000000..82fac171d5d1 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py @@ -0,0 +1,74 @@ +import unittest + + +def reverse(node): + prev = None + next = None + curr = node + + while curr: + next = curr.next + curr.next = prev + prev = curr + curr = next + + return prev + + +# Tests +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def test_short_linked_list(self): + second = Test.LinkedListNode(2) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [2, 1] + self.assertEqual(actual, expected) + + def test_long_linked_list(self): + sixth = Test.LinkedListNode(6) + fifth = Test.LinkedListNode(5, sixth) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [6, 5, 4, 3, 2, 1] + self.assertEqual(actual, expected) + + def test_one_element_linked_list(self): + first = Test.LinkedListNode(1) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [1] + self.assertEqual(actual, expected) + + def test_empty_linked_list(self): + result = reverse(None) + self.assertIsNone(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/stock-price.py b/users/wpcarro/scratch/deepmind/part_one/stock-price.py new file mode 100644 index 000000000000..7055b66af196 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/stock-price.py @@ -0,0 +1,51 @@ +def get_max_profit(xs): + best_profit = xs[1] - xs[0] + lowest_buy = xs[0] + + for x in xs[1:]: + best_profit = max(best_profit, x - lowest_buy) + lowest_buy = min(lowest_buy, x) + return best_profit + + +# Tests + +import unittest + + +class Test(unittest.TestCase): + def test_price_goes_up_then_down(self): + actual = get_max_profit([1, 5, 3, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_price_goes_down_then_up(self): + actual = get_max_profit([7, 2, 8, 9]) + expected = 7 + self.assertEqual(actual, expected) + + def test_price_goes_up_all_day(self): + actual = get_max_profit([1, 6, 7, 9]) + expected = 8 + self.assertEqual(actual, expected) + + def test_price_goes_down_all_day(self): + actual = get_max_profit([9, 7, 4, 1]) + expected = -2 + self.assertEqual(actual, expected) + + def test_price_stays_the_same_all_day(self): + actual = get_max_profit([1, 1, 1, 1]) + expected = 0 + self.assertEqual(actual, expected) + + def test_error_with_empty_prices(self): + with self.assertRaises(Exception): + get_max_profit([]) + + def test_error_with_one_price(self): + with self.assertRaises(Exception): + get_max_profit([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py b/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py new file mode 100644 index 000000000000..c01379295d32 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py @@ -0,0 +1,29 @@ +import unittest + + +def find_repeat(xs): + n = max(xs) + expected_sum = (n + 1) * n / 2 + actual_sum = sum(xs) + return actual_sum - expected_sum + + +# Tests +class Test(unittest.TestCase): + def test_short_list(self): + actual = find_repeat([1, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([4, 1, 3, 4, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([1, 5, 9, 7, 2, 6, 3, 8, 2, 4]) + expected = 2 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/.envrc b/users/wpcarro/scratch/deepmind/part_two/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py b/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py new file mode 100644 index 000000000000..03de0350d898 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py @@ -0,0 +1,126 @@ +import unittest +from collections import deque + + +# is_balanced :: Node(a) -> Bool +def is_balanced(node): + q = deque() + q.append((0, node)) + mn, mx = None, None + + while q: + depth, node = q.popleft() + # Current node is a leaf node + if not node.left and not node.right: + mx = depth if mx is None else max(mx, depth) + mn = depth if mn is None else min(mn, depth) + if mx - mn > 1: + return False + if node.left: + q.append((depth + 1, node.left)) + if node.right: + q.append((depth + 1, node.right)) + + return mx - mn <= 1 + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/bst-checker.py b/users/wpcarro/scratch/deepmind/part_two/bst-checker.py new file mode 100644 index 000000000000..fd0374a9ce91 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/bst-checker.py @@ -0,0 +1,110 @@ +import unittest +from collections import deque + + +# While this function solves the problem, it uses O(n) space since we're storing +# all of the less-thans and greater-thans. +def is_binary_search_tree_first_attempt(root): + q = deque() + q.append((set(), set(), root)) + + while q: + lts, gts, node = q.popleft() + + if not all([node.value < lt for lt in lts]): + return False + if not all([node.value > gt for gt in gts]): + return False + + if node.left: + q.append((lts | {node.value}, gts, node.left)) + if node.right: + q.append((lts, gts | {node.value}, node.right)) + + return True + + +# While I did not originally solve this problem this way, when I learned that I +# could condense the space of my solution's runtime, I wrote this. +def is_binary_search_tree(root): + q = deque() + q.append((None, None, root)) + + while q: + lt, gt, node = q.popleft() + + if not lt is None and node.value >= lt: + return False + if not gt is None and node.value <= gt: + return False + + if node.left: + q.append((node.value, gt, node.left)) + if node.right: + q.append((lt, node.value, node.right)) + + return True + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_valid_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_both_subtrees_valid(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(80) + left.insert_left(20) + left.insert_right(60) + right.insert_left(70) + right.insert_right(90) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_out_of_order_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(70) + right_right = right.insert_right(60) + right_right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_one_node_tree(self): + tree = Test.BinaryTreeNode(50) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py b/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py new file mode 100644 index 000000000000..0e31214b830d --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py @@ -0,0 +1,64 @@ +import unittest + + +# Solution +def is_first_come_first_served(xs, ys, zs): + i, j = 0, 0 + for z in zs: + if i < len(xs) and z == xs[i]: + i += 1 + elif j < len(ys) and z == ys[j]: + j += 1 + else: + return False + return i == len(xs) and j == len(ys) + + +# Tests +class Test(unittest.TestCase): + def test_both_registers_have_same_number_of_orders(self): + result = is_first_come_first_served([1, 4, 5], [2, 3, 6], + [1, 2, 3, 4, 5, 6]) + self.assertTrue(result) + + def test_registers_have_different_lengths(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 2, 6, 3, 5]) + self.assertFalse(result) + + def test_one_register_is_empty(self): + result = is_first_come_first_served([], [2, 3, 6], [2, 3, 6]) + self.assertTrue(result) + + def test_served_orders_is_missing_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 6, 3, 5]) + self.assertFalse(result) + + def test_served_orders_has_extra_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], + [1, 2, 3, 5, 6, 8]) + self.assertFalse(result) + + def test_one_register_has_extra_orders(self): + result = is_first_come_first_served([1, 9], [7, 8], [1, 7, 8]) + self.assertFalse(result) + + def test_one_register_has_unserved_orders(self): + result = is_first_come_first_served([55, 9], [7, 8], [1, 7, 8, 9]) + self.assertFalse(result) + + # Bonus + def test_handles_repeats(self): + actual = is_first_come_first_served([1, 2, 1], [3, 4, 5, 5], + [3, 4, 1, 5, 5, 2, 1]) + self.assertTrue(actual) + + def test_kitchen_didnt_serve(self): + actual = is_first_come_first_served([1, 2], [3, 4], [1, 3, 4]) + self.assertFalse(actual) + + def test_customer_didnt_pay(self): + actual = is_first_come_first_served([2], [3, 4], [1, 3, 4]) + self.assertFalse(actual) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/coin.ts b/users/wpcarro/scratch/deepmind/part_two/coin.ts new file mode 100644 index 000000000000..8aa8de8bb87a --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/coin.ts @@ -0,0 +1,102 @@ +// The denomination of a coin. +type Coin = number; + +// The amount of change remaining. +type Amount = number; + +// Mapping of Coin -> Int +type CoinBag = Map<Coin, number>; + +function createCoinBag(coins: Coin[]): CoinBag { + const result = new Map(); + + for (const coin of coins) { + result.set(coin, 0); + } + + return result; +} + +// This algorithm should work conceptual, but it does not actually +// work. JavaScript uses reference equality when constructing a Set<Map<A,B>>, +// so my result.size returns a higher number than I expect because it contains +// many duplicate entries. +// +// Conceptually, I'm not sure this solution is optimal either -- even after I +// can dedupe the entries in `result`. +function changePossibilities(amt: Amount, coins: Coin[]): number { + if (amt === 0) { + return 1; + } + const result: Set<CoinBag> = new Set(); + + const q: [Coin, Amount, CoinBag][] = []; + + for (const coin of coins) { + const bag = createCoinBag(coins); + bag.set(coin, 1); + q.push([coin, amt - coin, bag]); + } + + while (q.length > 0) { + const [coin, amt, bag] = q.shift(); + + console.log([coin, amt, bag]); + + if (amt === 0) { + result.add(bag); + } else if (amt < 0) { + continue; + } else { + for (const c of coins) { + const bagCopy = new Map(bag); + const value = bagCopy.get(c); + bagCopy.set(c, value + 1); + q.push([c, amt - c, bagCopy]); + } + } + } + console.log(result); + return result.size; +} + +// Tests +let desc = "sample input"; +let actual = changePossibilities(4, [1, 2, 3]); +let expected = 4; +assertEqual(actual, expected, desc); + +desc = "one way to make zero cents"; +actual = changePossibilities(0, [1, 2]); +expected = 1; +assertEqual(actual, expected, desc); + +desc = "no ways if no coins"; +actual = changePossibilities(1, []); +expected = 0; +assertEqual(actual, expected, desc); + +desc = "big coin value"; +actual = changePossibilities(5, [25, 50]); +expected = 0; +assertEqual(actual, expected, desc); + +desc = "big target amount"; +actual = changePossibilities(50, [5, 10]); +expected = 6; +assertEqual(actual, expected, desc); + +// I think InterviewCake designed this assertion to be computationally +// expensive. +desc = "change for one dollar"; +actual = changePossibilities(100, [1, 5, 10, 25, 50]); +expected = 292; +assertEqual(actual, expected, desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/delete-node.py b/users/wpcarro/scratch/deepmind/part_two/delete-node.py new file mode 100644 index 000000000000..4ed02ec30832 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/delete-node.py @@ -0,0 +1,57 @@ +import unittest + + +def delete_node(node): + if node.next: + node.value = node.next.value + node.next = node.next.next + else: + raise Exception( + "We cannot delete the last node in a linked list using this function" + ) + + +# Tests +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_node_at_beginning(self): + delete_node(self.first) + actual = self.first.get_values() + expected = [2, 3, 4] + self.assertEqual(actual, expected) + + def test_node_in_middle(self): + delete_node(self.second) + actual = self.first.get_values() + expected = [1, 3, 4] + self.assertEqual(actual, expected) + + def test_node_at_end(self): + with self.assertRaises(Exception): + delete_node(self.fourth) + + def test_one_node_in_list(self): + unique = Test.LinkedListNode(1) + with self.assertRaises(Exception): + delete_node(unique) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py new file mode 100644 index 000000000000..c9edc32c8856 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py @@ -0,0 +1,114 @@ +import unittest + + +################################################################################ +# InterviewCake's solution +################################################################################ +def cycle_len(xs, i): + """ + Returns the length of a cycle that contains no duplicate items. + """ + result = 1 + checkpt = i + current = xs[checkpt - 1] + + while current != checkpt: + current = xs[current - 1] + result += 1 + + return result + + +def theirs(xs): + """ + This is InterviewCake's solution. + """ + i = xs[-1] + for _ in range(len(xs) - 1): + i = xs[i - 1] + + cycle_length = cycle_len(xs, i) + + p0 = xs[-1] + p1 = xs[-1] + for _ in range(cycle_length): + p1 = xs[p1 - 1] + + while p0 != p1: + p0 = xs[p0 - 1] + p1 = xs[p1 - 1] + + print(p0, p1) + + return p0 + + +################################################################################ +# My solution +################################################################################ +def mine(xs): + """ + This is the solution that I came up with, which differs from InterviewCake's + solution. + """ + i = xs[-1] + offset = 1 if len(xs) % 2 == 0 else 2 + + for _ in range(len(xs) - offset): + i = xs[i - 1] + + return i + + +use_mine = True +find_duplicate = mine if use_mine else theirs + + +# Tests +class Test(unittest.TestCase): + def test_just_the_repeated_number(self): + # len(xs) even + actual = find_duplicate([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + # len(xs) even + actual = find_duplicate([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_medium_list(self): + # len(xs) even + actual = find_duplicate([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + # len(xs) odd + actual = find_duplicate([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + ############################################################################ + # Additional examples from InterviewCake.com + ############################################################################ + def test_example_a(self): + # len(xs) even + actual = find_duplicate([3, 4, 2, 3, 1, 5]) + expected = 3 + self.assertTrue(actual, expected) + + def test_example_b(self): + # len(xs) even + actual = find_duplicate([3, 1, 2, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_example_c(self): + # len(xs) odd BUT multiple duplicates + actual = find_duplicate([4, 3, 1, 1, 4]) + self.assertTrue(actual in {1, 4}) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts new file mode 100644 index 000000000000..98f5bb144e76 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts @@ -0,0 +1,70 @@ +function findRepeatBruteForce(xs: Array<number>): number { + // InterviewCake asks us to write a function that optimizes for space. Using + // brute force, we can write a function that returns an answer using constant + // (i.e. O(1)) space at the cost of a quadratic (i.e. O(n^2)) runtime. + // + // I did not think of this myself; InterviewCake's "Tell me more" hints + // did. Since I think this idea is clever, I wrote a solution from memory to + // help me internalize the solution. + for (let i = 0; i < xs.length; i += 1) { + let seeking = xs[i]; + for (let j = i + 1; j < xs.length; j += 1) { + if (xs[j] === seeking) { + return seeking; + } + } + } +} + +function findRepeatSort(xs: Array<number>): number { + // This version first sorts xs, which gives the function a time-complexity of + // O(n*log(n)), which is better than the quadratic complexity of the + // brute-force solution. The space requirement here is constant. + // + // Since we need to sort xs in-place to avoid paying a O(n) space cost for + // storing the newly sorted xs, we're mutating our input. InterviewCake + // advises us to not mutate our input. + xs.sort(); + let i = 0; + let j = 1; + for (; j < xs.length; ) { + if (xs[i] === xs[j]) { + return xs[i]; + } + i += 1; + j += 1; + } +} + +function findRepeat(xs: Array<number>): number { + return 0; +} + +// Tests +let desc = "just the repeated number"; +let actual = findRepeat([1, 1]); +let expected = 1; +assertEqual(actual, expected, desc); + +desc = "short array"; +actual = findRepeat([1, 2, 3, 2]); +expected = 2; +assertEqual(actual, expected, desc); + +desc = "medium array"; +actual = findRepeat([1, 2, 5, 5, 5, 5]); +expected = 5; +assertEqual(actual, expected, desc); + +desc = "long array"; +actual = findRepeat([4, 1, 4, 8, 3, 2, 7, 6, 5]); +expected = 4; +assertEqual(actual, expected, desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts b/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts new file mode 100644 index 000000000000..7bf1a484454d --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts @@ -0,0 +1,68 @@ +function findRotationPoint(xs: Array<string>): number { + // Find the rotation point in the vector. + let beg = 0; + let end = xs.length - 1; + + while (beg != end) { + let mid = beg + Math.floor((end - beg) / 2); + + if (beg === mid) { + return xs[beg] < xs[end] ? beg : end; + } + + if (xs[end] <= xs[mid]) { + beg = mid; + end = end; + } else { + beg = beg; + end = mid; + } + } + + return beg; +} + +// Tests +let desc; +let actual; +let expected; + +desc = "small array one"; +actual = findRotationPoint(["cape", "cake"]); +expected = 1; +assertEquals(actual, expected, desc); + +desc = "small array two"; +actual = findRotationPoint(["cake", "cape"]); +expected = 0; +assertEquals(actual, expected, desc); + +desc = "medium array"; +actual = findRotationPoint(["grape", "orange", "plum", "radish", "apple"]); +expected = 4; +assertEquals(actual, expected, desc); + +desc = "large array"; +actual = findRotationPoint([ + "ptolemaic", + "retrograde", + "supplant", + "undulate", + "xenoepist", + "asymptote", + "babka", + "banoffee", + "engender", + "karpatka", + "othellolagkage" +]); +expected = 5; +assertEquals(actual, expected, desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts b/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts new file mode 100644 index 000000000000..a0b6d5dbae53 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts @@ -0,0 +1,232 @@ +type Color = string; + +interface GraphNode { + label: string; + neighbors: Set<GraphNode>; + color: string; +} + +class GraphNode { + constructor(label: string) { + this.label = label; + this.neighbors = new Set(); + this.color = null; + } +} + +interface Queue<A> { + xs: Array<A>; +} + +class Queue<A> { + constructor() { + this.xs = []; + } + isEmpty(): boolean { + return this.xs.length === 0; + } + enqueue(x: A): void { + this.xs.push(x); + } + dequeue(): A { + return this.xs.shift(); + } +} + +type Graph = Array<GraphNode>; + +// Return a set of all of the colors from the neighbor nodes of `node`. +function neighborColors(node: GraphNode): Set<Color> { + const result: Set<Color> = new Set(); + + for (const x of node.neighbors) { + if (typeof x.color === 'string') { + result.add(x.color); + } + } + + return result; +} + +// Returns the set difference between sets `xs`, and `ys`. +function setDifference<A>(xs: Set<A>, ys: Set<A>): Set<A> { + const result: Set<A> = new Set(); + + for (const x of xs) { + if (!ys.has(x)) { + result.add(x); + } + } + + return result; +} + +// Returns an element from the set, `xs`. +// Throwns an error if `xs` is an empty set. +function choose<A>(xs: Set<A>): A { + if (xs.size === 0) { + throw new Error('Cannot choose an element from an empty set.'); + } else { + return xs.values().next().value; + } +} + +// Returns true if `node` is present in `node.neighbors`. +function isCyclic(node: GraphNode): boolean { + for (const x of node.neighbors) { + if (x === node) { + return true; + } + } +} + +function colorGraph(graph: Graph, colors: Array<Color>): void { + const allColors = new Set(colors); + + for (const node of graph) { + if (isCyclic(node)) { + throw new Error('InterviewCake would like me to invalidate this'); + } + if (typeof node.color !== 'string') { + node.color = choose(setDifference(allColors, neighborColors(node))); + } + } +} + + +// Tests +const colors = ['red', 'green', 'blue', 'orange', 'yellow', 'white']; + +let graph = []; +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + nodeA.neighbors.add(nodeB); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeC.neighbors.add(nodeB); + nodeC.neighbors.add(nodeD); + nodeD.neighbors.add(nodeC); + graph = [nodeA, nodeB, nodeC, nodeD]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'line graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + nodeA.neighbors.add(nodeB); + nodeB.neighbors.add(nodeA); + nodeC.neighbors.add(nodeD); + nodeD.neighbors.add(nodeC); + graph = [nodeA, nodeB, nodeC, nodeD]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'separate graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + nodeA.neighbors.add(nodeB); + nodeA.neighbors.add(nodeC); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeC.neighbors.add(nodeA); + nodeC.neighbors.add(nodeB); + graph = [nodeA, nodeB, nodeC]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'triangle graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + const nodeE = new GraphNode('E'); + nodeA.neighbors.add(nodeB); + nodeA.neighbors.add(nodeC); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeB.neighbors.add(nodeD); + nodeB.neighbors.add(nodeE); + nodeC.neighbors.add(nodeA); + nodeC.neighbors.add(nodeB); + nodeC.neighbors.add(nodeD); + nodeC.neighbors.add(nodeE); + nodeD.neighbors.add(nodeB); + nodeD.neighbors.add(nodeC); + nodeD.neighbors.add(nodeE); + nodeE.neighbors.add(nodeB); + nodeE.neighbors.add(nodeC); + nodeE.neighbors.add(nodeD); + graph = [nodeA, nodeB, nodeC, nodeD, nodeE]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'envelope graph'); + +{ + const nodeA = new GraphNode('A'); + nodeA.neighbors.add(nodeA); + graph = [nodeA]; +} +assertThrows(() => { + colorGraph(graph, colors); +}, 'loop graph'); + +function validateGraphColoring(graph) { + + const maxDegree = Math.max(...graph.map(node => node.neighbors.size)); + + const colorsUsed = new Set(); + + graph.forEach(node => { + colorsUsed.add(node.color); + }); + + if (colorsUsed.has(null)) { + return false; + } + + if (colorsUsed.size > maxDegree + 1) { + return false; + } + + let badEdges = 0; + + graph.forEach(node => { + node.neighbors.forEach(neighbor => { + if (neighbor.color === node.color) { + badEdges += 1; + } + }); + }); + + if (badEdges > 0) { + return false; + } + + return true; +} + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} + +function assertThrows(func, desc) { + try { + func(); + console.log(`${desc} ... FAIL`); + } catch (e) { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py b/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py new file mode 100644 index 000000000000..8ebb5cf29a4f --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py @@ -0,0 +1,81 @@ +import unittest +import sys +import trace + + +def highest_product_of_3(xs): + if len(xs) < 3: + raise Exception("List needs to contain at least three elements.") + hp3 = xs[0] * xs[1] * xs[2] + hp2 = xs[0] * xs[1] + lp2 = xs[0] * xs[1] + hn = max(xs[0], xs[1]) + ln = min(xs[0], xs[1]) + for x in xs[2:]: + hp3 = max(hp3, hp2 * x, lp2 * x) + hp2 = max(hp2, hn * x, ln * x) + lp2 = min(lp2, hn * x, ln * x) + hn = max(hn, x) + ln = min(ln, x) + return hp3 + + +# Tests +class Test(unittest.TestCase): + def test_short_list(self): + actual = highest_product_of_3([1, 2, 3, 4]) + expected = 24 + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = highest_product_of_3([6, 1, 3, 5, 7, 8, 2]) + expected = 336 + self.assertEqual(actual, expected) + + def test_list_has_one_negative(self): + actual = highest_product_of_3([-5, 4, 8, 2, 3]) + expected = 96 + self.assertEqual(actual, expected) + + def test_list_has_two_negatives(self): + actual = highest_product_of_3([-10, 1, 3, 2, -10]) + expected = 300 + self.assertEqual(actual, expected) + + def test_list_is_all_negatives(self): + actual = highest_product_of_3([-5, -1, -3, -2]) + expected = -6 + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + highest_product_of_3([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + highest_product_of_3([1]) + + def test_error_with_two_numbers(self): + with self.assertRaises(Exception): + highest_product_of_3([1, 1]) + + def test_custom(self): + actual = highest_product_of_3([9, 5, 2, 1, 7, 3]) + expected = 9 * 7 * 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) + + +def main(): + highest_product_of_3([-5, -1, -3, -2]) + + +tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix], + trace=0, + count=1) + +tracer.run('main()') +r = tracer.results() +r.write_results(show_missing=True, coverdir=".") diff --git a/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts b/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts new file mode 100644 index 000000000000..d6da1db3d313 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts @@ -0,0 +1,85 @@ +function canTwoMoviesFillFlightBonus( + xs: Array<number>, + duration: number +): boolean { + // Returns true if two movies exist that can fill the flight duration +/- 20 + // minutes. + const seeking = {}; + + for (let x of xs) { + for (let i = 0; i < 40; i += 1) { + if (seeking[x + i + 1]) { + return true; + } + } + for (let i = 1; i <= 20; i += 1) { + seeking[duration - x - i] = true; + seeking[duration - x + i] = true; + } + } + + return false; +} + +function canTwoMoviesFillFlight(xs: Array<number>, duration: number): boolean { + const seeking = {}; + + for (let x of xs) { + if (seeking[x]) { + return true; + } else { + seeking[duration - x] = true; + } + } + + return false; +} + +// Tests +let desc = "short flight"; +let actual = canTwoMoviesFillFlight([2, 4], 1); +let expected = false; +assertEquals(actual, expected, desc); + +desc = "long flight"; +actual = canTwoMoviesFillFlight([2, 4], 6); +expected = true; +assertEquals(actual, expected, desc); + +desc = "one movie half flight length"; +actual = canTwoMoviesFillFlight([3, 8], 6); +expected = false; +assertEquals(actual, expected, desc); + +desc = "two movies half flight length"; +actual = canTwoMoviesFillFlight([3, 8, 3], 6); +expected = true; +assertEquals(actual, expected, desc); + +desc = "lots of possible pairs"; +actual = canTwoMoviesFillFlight([1, 2, 3, 4, 5, 6], 7); +expected = true; +assertEquals(actual, expected, desc); + +desc = "not using first movie"; +actual = canTwoMoviesFillFlight([4, 3, 2], 5); +expected = true; +assertEquals(actual, expected, desc); + +desc = "only one movie"; +actual = canTwoMoviesFillFlight([6], 6); +expected = false; +assertEquals(actual, expected, desc); + +desc = "no movies"; +actual = canTwoMoviesFillFlight([], 2); +expected = false; +assertEquals(actual, expected, desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts b/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts new file mode 100644 index 000000000000..2d478e0e37de --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts @@ -0,0 +1,63 @@ +function mergeArrays(xs: Array<number>, ys: Array<number>): Array<number> { + let i = 0; + let j = 0; + const result = []; + + for (let q = 0; q < xs.length + ys.length; q += 1) { + if (i === xs.length) { + while (j < ys.length) { + result.push(ys[j]); + j += 1; + } + } else if (j === ys.length) { + while (i < xs.length) { + result.push(xs[i]); + i += 1; + } + } else if (xs[i] < ys[j]) { + result.push(xs[i]); + i += 1; + } else { + result.push(ys[j]); + j += 1; + } + } + + return result; +} + +// Tests +let desc = "both arrays are empty"; +let actual = mergeArrays([], []); +let expected = []; +assertDeepEqual(actual, expected, desc); + +desc = "first array is empty"; +actual = mergeArrays([], [1, 2, 3]); +expected = [1, 2, 3]; +assertDeepEqual(actual, expected, desc); + +desc = "second array is empty"; +actual = mergeArrays([5, 6, 7], []); +expected = [5, 6, 7]; +assertDeepEqual(actual, expected, desc); + +desc = "both arrays have some numbers"; +actual = mergeArrays([2, 4, 6], [1, 3, 7]); +expected = [1, 2, 3, 4, 6, 7]; +assertDeepEqual(actual, expected, desc); + +desc = "arrays are different lengths"; +actual = mergeArrays([2, 4, 6, 8], [1, 7]); +expected = [1, 2, 4, 6, 7, 8]; +assertDeepEqual(actual, expected, desc); + +function assertDeepEqual(a: Array<number>, b: Array<number>, desc: string) { + const aStr = JSON.stringify(a); + const bStr = JSON.stringify(b); + if (aStr !== bStr) { + console.log(`${desc} ... FAIL: ${aStr} != ${bStr}`); + } else { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py b/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py new file mode 100644 index 000000000000..23d0813d1524 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py @@ -0,0 +1,115 @@ +import unittest +import timeit + + +# Solution that uses O(n) space to store the result. +def not_in_place(xs): + xs.sort() + result = [xs[0]] + for ca, cb in xs[1:]: + pa, pb = result[-1] + if ca <= pb: + result[-1] = (pa, max(pb, cb)) + else: + result.append((ca, cb)) + return result + + +# Solution that uses O(1) space to store the result. +def in_place(xs): + xs.sort() + i = 0 + j = i + 1 + while j < len(xs): + pa, pb = xs[i] + ca, cb = xs[j] + if ca <= pb: + xs[i] = (pa, max(pb, cb)) + del xs[j] + else: + i = j + j += 1 + return xs + + +def test_nip(): + inputs = [ + [(1, 3), (2, 4)], + [(5, 6), (6, 8)], + [(1, 8), (2, 5)], + [(1, 3), (4, 8)], + [(1, 4), (2, 5), (5, 8)], + [(5, 8), (1, 4), (6, 8)], + [(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)], + [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)], + ] + for x in inputs: + not_in_place(x) + + +def test_ip(): + inputs = [ + [(1, 3), (2, 4)], + [(5, 6), (6, 8)], + [(1, 8), (2, 5)], + [(1, 3), (4, 8)], + [(1, 4), (2, 5), (5, 8)], + [(5, 8), (1, 4), (6, 8)], + [(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)], + [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)], + ] + for x in inputs: + in_place(x) + + +merge_ranges = in_place + +setup = 'from __main__ import test_nip, test_ip' +print(timeit.timeit('test_nip()', number=10000, setup=setup)) +print(timeit.timeit('test_ip()', number=10000, setup=setup)) + + +# Tests +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/mesh-message.py b/users/wpcarro/scratch/deepmind/part_two/mesh-message.py new file mode 100644 index 000000000000..a265296ab066 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/mesh-message.py @@ -0,0 +1,183 @@ +import unittest +from collections import deque +from heapq import heappush, heappop + + +################################################################################ +# InterviewCake.com +################################################################################ +# construct_path :: Map String String -> String -> String -> [String] +def construct_path(paths, beg, end): + """ + Reconstruct the path from `beg` to `end`. + """ + result = [] + current = end + + print(paths) + print(beg, end) + print('-----') + while current: + result.append(current) + current = paths[current] + + result.reverse() + return result + + +def get_path_ic(graph, beg, end): + """ + InterviewCake uses a dictionary and back-tracking to store and reconstruct + the path instead of storing the path as state on each node. + This reduces the memory costs. See get_path_bft for an example of this less + optimal solution. + """ + if beg not in graph: + raise Exception('Origin node absent from graph.') + + if end not in graph: + raise Exception('Destination node absent from graph.') + + q = deque() + q.append(beg) + paths = {beg: None} + + while q: + node = q.popleft() + + if node == end: + print(graph) + return construct_path(paths, beg, end) + + for x in graph[node]: + if x not in paths: + paths[x] = node + q.append(x) + + return None + + +################################################################################ +# Per-node state +################################################################################ +def get_path_bft(graph, beg, end): + """ + Here we find the shortest path from `beg` to `end` in `graph` by doing a BFT + from beg to end and storing the path state alongside each node in the queue. + """ + if beg not in graph: + raise Exception('Origin node absent from graph.') + + if end not in graph: + raise Exception('Destination node absent from graph.') + + q = deque() + seen = set() + q.append([beg]) + + while q: + path = q.popleft() + node = path[-1] + seen.add(node) + + if node == end: + return path + + for x in graph[node]: + if x not in seen: + q.append(path + [x]) + + +################################################################################ +# Dijkstra's Algorithm +################################################################################ +def get_path(graph, beg, end): + """ + Here we find the shortest path using Dijkstra's algorithm, which is my + favorite solution. + """ + if beg not in graph: + raise Exception( + 'The origin node, {}, is not present in the graph'.format(beg)) + + if end not in graph: + raise Exception( + 'The origin node, {}, is not present in the graph'.format(end)) + + q = [] + seen = set() + heappush(q, (1, [beg])) + + while q: + weight, path = heappop(q) + node = path[-1] + seen.add(node) + + if node == end: + return path + + for x in graph[node]: + if x not in seen: + heappush(q, (weight + 1, path + [x])) + + return None + + +# Tests +class Test(unittest.TestCase): + def setUp(self): + self.graph = { + 'a': ['b', 'c', 'd'], + 'b': ['a', 'd'], + 'c': ['a', 'e'], + 'd': ['b', 'a'], + 'e': ['c'], + 'f': ['g'], + 'g': ['f'], + } + + def test_two_hop_path_1(self): + actual = get_path(self.graph, 'a', 'e') + expected = ['a', 'c', 'e'] + self.assertEqual(actual, expected) + + def test_two_hop_path_2(self): + actual = get_path(self.graph, 'd', 'c') + expected = ['d', 'a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_1(self): + actual = get_path(self.graph, 'a', 'c') + expected = ['a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_2(self): + actual = get_path(self.graph, 'f', 'g') + expected = ['f', 'g'] + self.assertEqual(actual, expected) + + def test_one_hop_path_3(self): + actual = get_path(self.graph, 'g', 'f') + expected = ['g', 'f'] + self.assertEqual(actual, expected) + + def test_zero_hop_path(self): + actual = get_path(self.graph, 'a', 'a') + expected = ['a'] + self.assertEqual(actual, expected) + + def test_no_path(self): + actual = get_path(self.graph, 'a', 'f') + expected = None + self.assertEqual(actual, expected) + + def test_start_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'h', 'a') + + def test_end_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'a', 'h') + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py b/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py new file mode 100644 index 000000000000..52354f990e11 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py @@ -0,0 +1,104 @@ +# Herein I'm practicing two-dimensional matrix traversals in all directions of +# which I can conceive: +# 0. T -> B; L -> R +# 1. T -> B; R -> L +# 2. B -> T; L -> R +# 3. B -> T; R -> L +# +# Commentary: +# When I think of matrices, I'm reminded of cartesian planes. I think of the +# cells as (X,Y) coordinates. This has been a pitfall for me because matrices +# are usually encoded in the opposite way. That is, to access a cell at the +# coordinates (X,Y) given a matrix M, you index M like this: M[Y][X]. To attempt +# to avoid this confusion, instead of saying X and Y, I will prefer saying +# "column" and "row". +# +# When traversing a matrix, you typically traverse vertically and then +# horizontally; in other words, the rows come first followed by the columns. As +# such, I'd like to refer to traversal orders as "top-to-bottom, left-to-right" +# rather than "left-to-right, top-to-bottom". +# +# These practices are all in an attempt to rewire my thinking. + +# This is a list of matrices where the index of a matrix corresponds to the +# order in which it should be traversed to produce the sequence: +# [1,2,3,4,5,6,7,8,9]. +boards = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[3, 2, 1], [6, 5, 4], [9, 8, 7]], + [[7, 8, 9], [4, 5, 6], [1, 2, 3]], [[9, 8, 7], [6, 5, 4], [3, 2, 1]]] + +# T -> B; L -> R +board = boards[0] +result = [] +for row in board: + for col in row: + result.append(col) +print(result) + +# T -> B; R -> L +board = boards[1] +result = [] +for row in board: + for col in reversed(row): + result.append(col) +print(result) + +# B -> T; L -> R +board = boards[2] +result = [] +for row in reversed(board): + for col in row: + result.append(col) +print(result) + +# B -> T; R -> L +board = boards[3] +result = [] +for row in reversed(board): + for col in reversed(row): + result.append(col) +print(result) + +################################################################################ +# Neighbors +################################################################################ + +import random + + +# Generate a matrix of size `rows` x `cols` where each cell contains an item +# randomly selected from `xs`. +def generate_board(rows, cols, xs): + result = [] + for _ in range(rows): + row = [] + for _ in range(cols): + row.append(random.choice(xs)) + result.append(row) + return result + + +# Print the `board` to the screen. +def print_board(board): + print('\n'.join([' '.join(row) for row in board])) + + +board = generate_board(4, 5, ['R', 'G', 'B']) +print_board(board) + + +# Return all of the cells horizontally and vertically accessible from a starting +# cell at `row`, `col` in `board`. +def neighbors(row, col, board): + result = {'top': [], 'bottom': [], 'left': [], 'right': []} + for i in range(row - 1, -1, -1): + result['top'].append(board[i][col]) + for i in range(row + 1, len(board)): + result['bottom'].append(board[i][col]) + for i in range(col - 1, -1, -1): + result['left'].append(board[row][i]) + for i in range(col + 1, len(board[0])): + result['right'].append(board[row][i]) + return result + + +print(neighbors(1, 2, board)) diff --git a/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py b/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py new file mode 100644 index 000000000000..14e176b62aab --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py @@ -0,0 +1,72 @@ +import unittest + + +# Compute the fibonacci using a bottom-up algorithm. +def fib(n): + if n < 0: + raise Error('Cannot call fibonacci with negative values') + cache = [0, 1] + for i in range(n): + cache[0], cache[1] = cache[1], cache[0] + cache[1] + return cache[0] + + +# Compute the fibonacci using memoization. +def fib_memoized(n): + cache = { + 0: 0, + 1: 1, + } + + def do_fib(n): + if n < 0: + raise Error('The fib function does not support negative inputs') + + if n in cache: + return cache[n] + + cache[n - 1] = do_fib(n - 1) + cache[n - 2] = do_fib(n - 2) + return cache[n - 1] + cache[n - 2] + + return do_fib(n) + + +# Tests +class Test(unittest.TestCase): + def test_zeroth_fibonacci(self): + actual = fib(0) + expected = 0 + self.assertEqual(actual, expected) + + def test_first_fibonacci(self): + actual = fib(1) + expected = 1 + self.assertEqual(actual, expected) + + def test_second_fibonacci(self): + actual = fib(2) + expected = 1 + self.assertEqual(actual, expected) + + def test_third_fibonacci(self): + actual = fib(3) + expected = 2 + self.assertEqual(actual, expected) + + def test_fifth_fibonacci(self): + actual = fib(5) + expected = 5 + self.assertEqual(actual, expected) + + def test_tenth_fibonacci(self): + actual = fib(10) + expected = 55 + self.assertEqual(actual, expected) + + def test_negative_fibonacci(self): + with self.assertRaises(Exception): + fib(-1) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/package-lock.json b/users/wpcarro/scratch/deepmind/part_two/package-lock.json new file mode 100644 index 000000000000..340aad9f5ce2 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/package-lock.json @@ -0,0 +1,79 @@ +{ + "name": "deepmind-part-two", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "prettier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ts-node": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", + "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/package.json b/users/wpcarro/scratch/deepmind/part_two/package.json new file mode 100644 index 000000000000..1f10668ec861 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/package.json @@ -0,0 +1,16 @@ +{ + "name": "deepmind-part-two", + "version": "1.0.0", + "description": "Practicing coding interview questions", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "William Carroll", + "license": "MIT", + "devDependencies": { + "prettier": "^2.0.2", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py b/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py new file mode 100644 index 000000000000..730b4bfdc873 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py @@ -0,0 +1,37 @@ +import unittest +from collections import Counter + + +def has_palindrome_permutation(xs): + vs = Counter(xs).values() + return len([v for v in vs if v % 2 == 1]) in {0, 1} + + +# Tests +class Test(unittest.TestCase): + def test_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcbcd') + self.assertTrue(result) + + def test_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabccbdd') + self.assertTrue(result) + + def test_no_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcd') + self.assertFalse(result) + + def test_no_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabbcd') + self.assertFalse(result) + + def test_empty_string(self): + result = has_palindrome_permutation('') + self.assertTrue(result) + + def test_one_character_string(self): + result = has_palindrome_permutation('a') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py b/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py new file mode 100644 index 000000000000..6f7858ff4e5b --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py @@ -0,0 +1,68 @@ +import unittest + + +# get_products_of_all_ints_except_at_index :: [Int] -> [Int] +def get_products_of_all_ints_except_at_index(xs): + n = len(xs) + if n < 2: + raise Exception("Cannot computer without 2 or elements") + # lhs + befores = [None] * n + befores[0] = 1 + for i in range(1, n): + befores[i] = befores[i - 1] * xs[i - 1] + + # rhs + afters = [None] * n + afters[-1] = 1 + for i in range(n - 2, -1, -1): + afters[i] = afters[i + 1] * xs[i + 1] + + result = [None] * n + for i in range(n): + result[i] = befores[i] * afters[i] + return result + + +# Tests +class Test(unittest.TestCase): + def test_small_list(self): + actual = get_products_of_all_ints_except_at_index([1, 2, 3]) + expected = [6, 3, 2] + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = get_products_of_all_ints_except_at_index([8, 2, 4, 3, 1, 5]) + expected = [120, 480, 240, 320, 960, 192] + self.assertEqual(actual, expected) + + def test_list_has_one_zero(self): + actual = get_products_of_all_ints_except_at_index([6, 2, 0, 3]) + expected = [0, 0, 36, 0] + self.assertEqual(actual, expected) + + def test_list_has_two_zeros(self): + actual = get_products_of_all_ints_except_at_index([4, 0, 9, 1, 0]) + expected = [0, 0, 0, 0, 0] + self.assertEqual(actual, expected) + + def test_one_negative_number(self): + actual = get_products_of_all_ints_except_at_index([-3, 8, 4]) + expected = [32, -12, -24] + self.assertEqual(actual, expected) + + def test_all_negative_numbers(self): + actual = get_products_of_all_ints_except_at_index([-7, -1, -4, -2]) + expected = [-8, -56, -14, -28] + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts b/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts new file mode 100644 index 000000000000..cb930d9ad648 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts @@ -0,0 +1,85 @@ +// Returns a new string comprised of every characters in `xs` except for the +// character at `i`. +function everyOtherChar(xs: string, i: number): string[] { + const result = []; + + for (let j = 0; j < xs.length; j += 1) { + if (i !== j) { + result.push(xs[j]); + } + } + + return [xs[i], result.join('')]; +} + +function getPermutations(xs: string): Set<string> { + if (xs === '') { + return new Set(['']); + } + + const result: Set<string> = new Set; + + for (let i = 0; i < xs.length; i += 1) { + const [char, rest] = everyOtherChar(xs, i); + const perms = getPermutations(rest); + + for (const perm of perms) { + result.add(char + perm); + } + } + + return result; +} + +// Tests +let desc = 'empty string'; +let input = ''; +let actual = getPermutations(input); +let expected = new Set(['']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'one character string'; +input = 'a'; +actual = getPermutations(input); +expected = new Set(['a']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'two character string'; +input = 'ab'; +actual = getPermutations(input); +expected = new Set(['ab', 'ba']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'three character string'; +input = 'abc'; +actual = getPermutations(input); +expected = new Set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'four character string'; +input = 'abca'; +actual = getPermutations(input); +expected = new Set([ + 'abca', 'abac', 'acba', 'acab', 'aabc', 'aacb', 'baca', 'baac', 'bcaa', + 'bcaa', 'baac', 'baca', 'caba', 'caab', 'cbaa', 'cbaa', 'caab', 'caba', + 'aabc', 'aacb', 'abac', 'abca', 'acab', 'acba' +]); +assert(isSetsEqual(actual, expected), desc); + +function isSetsEqual(as, bs) { + if (as.size !== bs.size) { + return false; + } + for (let a of as) { + if (!bs.has(a)) return false; + } + return true; +} + +function assert(condition, desc) { + if (condition) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts b/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts new file mode 100644 index 000000000000..d714dfef997f --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts @@ -0,0 +1,13 @@ +// Reverse array of characters, `xs`, mutatively. +function reverse(xs: Array<string>) { + let i: number = 0; + let j: number = xs.length - 1; + + while (i < j) { + let tmp = xs[i]; + xs[i] = xs[j] + xs[j] = tmp + i += 1 + j -= 1 + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/reverse-words.py b/users/wpcarro/scratch/deepmind/part_two/reverse-words.py new file mode 100644 index 000000000000..033d11244ca7 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/reverse-words.py @@ -0,0 +1,74 @@ +import unittest + + +def reverse(xs, i, j): + """Reverse array of characters, xs, in-place.""" + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + + +def reverse_words(xs): + punctuation = None + if len(xs) > 0 and xs[-1] in ".?!": + punctuation = xs.pop() + reverse(xs, 0, len(xs) - 1) + i = 0 + j = i + while j < len(xs): + while j < len(xs) and xs[j] != ' ': + j += 1 + reverse(xs, i, j - 1) + j += 1 + i = j + if punctuation: + xs.append(punctuation) + + +# Tests +class Test(unittest.TestCase): + def test_one_word(self): + message = list('vault') + reverse_words(message) + expected = list('vault') + self.assertEqual(message, expected) + + def test_two_words(self): + message = list('thief cake') + reverse_words(message) + expected = list('cake thief') + self.assertEqual(message, expected) + + def test_three_words(self): + message = list('one another get') + reverse_words(message) + expected = list('get another one') + self.assertEqual(message, expected) + + def test_multiple_words_same_length(self): + message = list('rat the ate cat the') + reverse_words(message) + expected = list('the cat ate the rat') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('yummy is cake bundt chocolate') + reverse_words(message) + expected = list('chocolate bundt cake is yummy') + self.assertEqual(message, expected) + + def test_empty_string(self): + message = list('') + reverse_words(message) + expected = list('') + self.assertEqual(message, expected) + + def test_bonus_support_punctuation(self): + message = list('yummy is cake bundt chocolate this!') + reverse_words(message) + expected = list('this chocolate bundt cake is yummy!') + self.assertEqual(message, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts b/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts new file mode 100644 index 000000000000..4c5e57607d87 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts @@ -0,0 +1,219 @@ +/******************************************************************************* + * Setup + ******************************************************************************/ + +interface BinaryTreeNode { + value: number; + left: BinaryTreeNode; + right: BinaryTreeNode; +} + +class BinaryTreeNode { + constructor(value: number) { + this.value = value; + this.left = null; + this.right = null; + } + + insertLeft(value: number): BinaryTreeNode { + this.left = new BinaryTreeNode(value); + return this.left; + } + + insertRight(value: number): BinaryTreeNode { + this.right = new BinaryTreeNode(value); + return this.right; + } +} + +/******************************************************************************* + * First solution + ******************************************************************************/ + +/** + * I first solved this problem using O(n) space and O(n*log(n)) + * time. InterviewCake informs me that we can improve both the time and the + * space performance. + */ +function findSecondLargest_first(node: BinaryTreeNode): number { + const stack: Array<BinaryTreeNode> = []; + const xs: Array<number> = []; + stack.push(node); + + while (stack.length > 0) { + const node = stack.pop() + + xs.push(node.value); + + if (node.left) { + stack.push(node.left); + } + if (node.right) { + stack.push(node.right); + } + } + + xs.sort(); + + if (xs.length < 2) { + throw new Error('Cannot find the second largest element in a BST with fewer than two elements.'); + } else { + return xs[xs.length - 2]; + } +} + +/******************************************************************************* + * Second solution + ******************************************************************************/ + +/** + * My second solution accumulates a list of the values in the tree using an + * in-order traversal. This reduces the runtime costs from O(n*log(n)) from the + * previous solution to O(n). The memory cost is still O(n), which InterviewCake + * informs me can be reduced to O(1). + */ +function findSecondLargest_second(node: BinaryTreeNode): number { + const xs: Array<number> = accumulateInorder(node); + + if (xs.length < 2) { + throw new Error('Cannot find the second largest element in a BST with fewer than two elements.'); + } else { + return xs[xs.length - 2]; + } +} + +/** + * Returns an array containing the values of the tree, `node`, sorted in-order + * (i.e. from smallest-to-largest). + */ +function accumulateInorder(node: BinaryTreeNode): Array<number> { + let result = []; + + if (node.left) { + result = result.concat(accumulateInorder(node.left)); + } + result.push(node.value) + if (node.right) { + result = result.concat(accumulateInorder(node.right)); + } + + return result; +} + +/******************************************************************************* + * Third solution + ******************************************************************************/ + +/** + * Returns the largest number in a BST. + */ +function findLargest(node: BinaryTreeNode): number { + let curr: BinaryTreeNode = node; + + while (curr.right) { + curr = curr.right; + } + + return curr.value; +} + +/** + * Returns the second largest number in a BST + */ +function findSecondLargest(node: BinaryTreeNode): number { + let curr = node; + let parent = null; + + while (curr.right) { + parent = curr; + curr = curr.right + } + + if (curr.left) { + return findLargest(curr.left); + } + else { + return parent.value; + } +} + + +// Tests +let desc = 'full tree'; +let treeRoot = new BinaryTreeNode(50); +let leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +let rightNode = treeRoot.insertRight(70); +rightNode.insertLeft(60); +rightNode.insertRight(80); +assertEquals(findSecondLargest(treeRoot), 70, desc); + +desc = 'largest has a left child'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +rightNode.insertLeft(60); +assertEquals(findSecondLargest(treeRoot), 60, desc); + +desc = 'largest has a left subtree'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +leftNode = rightNode.insertLeft(60); +leftNode.insertRight(65); +leftNode = leftNode.insertLeft(55); +leftNode.insertRight(58); +assertEquals(findSecondLargest(treeRoot), 65, desc); + +desc = 'second largest is root node'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +assertEquals(findSecondLargest(treeRoot), 50, desc); + +desc = 'descending linked list'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(40); +leftNode = leftNode.insertLeft(30); +leftNode = leftNode.insertLeft(20); +leftNode = leftNode.insertLeft(10); +assertEquals(findSecondLargest(treeRoot), 40, desc); + +desc = 'ascending linked list'; +treeRoot = new BinaryTreeNode(50); +rightNode = treeRoot.insertRight(60); +rightNode = rightNode.insertRight(70); +rightNode = rightNode.insertRight(80); +assertEquals(findSecondLargest(treeRoot), 70, desc); + +desc = 'one node tree'; +treeRoot = new BinaryTreeNode(50); +assertThrowsError(() => findSecondLargest(treeRoot), desc); + +desc = 'when tree is empty'; +treeRoot = null; +assertThrowsError(() => findSecondLargest(treeRoot), desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`) + } +} + +function assertThrowsError(func, desc) { + try { + func(); + console.log(`${desc} ... FAIL`); + } catch (e) { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/shell.nix b/users/wpcarro/scratch/deepmind/part_two/shell.nix new file mode 100644 index 000000000000..6080171bb835 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/shell.nix @@ -0,0 +1,11 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + python3 + go + goimports + ]; +} diff --git a/users/wpcarro/scratch/deepmind/part_two/shuffle.py b/users/wpcarro/scratch/deepmind/part_two/shuffle.py new file mode 100644 index 000000000000..fdc5a8bd80ab --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/shuffle.py @@ -0,0 +1,20 @@ +import random + + +def get_random(floor, ceiling): + return random.randrange(floor, ceiling + 1) + + +def shuffle(xs): + n = len(xs) + for i in range(n - 1): + j = get_random(i + 1, n - 1) + xs[i], xs[j] = xs[j], xs[i] + + +sample_list = [1, 2, 3, 4, 5] +print('Sample list:', sample_list) + +print('Shuffling sample list...') +shuffle(sample_list) +print(sample_list) diff --git a/users/wpcarro/scratch/deepmind/part_two/stock-price.py b/users/wpcarro/scratch/deepmind/part_two/stock-price.py new file mode 100644 index 000000000000..56a3c20ea05b --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/stock-price.py @@ -0,0 +1,54 @@ +import unittest + + +def get_max_profit(xs): + if len(xs) < 2: + raise Exception('Can only trade with two or more ticker values.') + lowest_buy = xs[0] + max_profit = None + for x in xs[1:]: + if not max_profit: + max_profit = x - lowest_buy + else: + max_profit = max(max_profit, x - lowest_buy) + lowest_buy = min(lowest_buy, x) + return max_profit + + +# Tests +class Test(unittest.TestCase): + def test_price_goes_up_then_down(self): + actual = get_max_profit([1, 5, 3, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_price_goes_down_then_up(self): + actual = get_max_profit([7, 2, 8, 9]) + expected = 7 + self.assertEqual(actual, expected) + + def test_price_goes_up_all_day(self): + actual = get_max_profit([1, 6, 7, 9]) + expected = 8 + self.assertEqual(actual, expected) + + def test_price_goes_down_all_day(self): + actual = get_max_profit([9, 7, 4, 1]) + expected = -2 + self.assertEqual(actual, expected) + + def test_price_stays_the_same_all_day(self): + actual = get_max_profit([1, 1, 1, 1]) + expected = 0 + self.assertEqual(actual, expected) + + def test_error_with_empty_prices(self): + with self.assertRaises(Exception): + get_max_profit([]) + + def test_error_with_one_price(self): + with self.assertRaises(Exception): + get_max_profit([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/todo.org b/users/wpcarro/scratch/deepmind/part_two/todo.org new file mode 100644 index 000000000000..9c76da754167 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/todo.org @@ -0,0 +1,77 @@ +* Array and string manipulation +** DONE Merging Meeting Times +** DONE Reverse String in Place +** DONE Reverse Words +** DONE Merge Sorted Arrays +** DONE Cafe Order Checker +* Hashing and hash tables +** DONE Inflight Entertainment +** DONE Permutation Palindrome +** DONE Word Cloud Data +** DONE Top Scores +* Greedy Algorithms +** DONE Apple Stocks +** DONE Highest Product of 3 +** DONE Product of All Other Numbers +** DONE Cafe Order Checker +** DONE In-Place Shuffle +* Sorting, searching, and logarithms +** DONE Find Rotation Point +** TODO Find Repeat, Space Edition +** DONE Top Scores +** DONE Merging Meeting Times +* Trees and graphs +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +** DONE Graph Coloring +** DONE MeshMessage +** DONE Find Repeat, Space Edition BEAST MODE +* Dynamic programming and recursion +** DONE Recursive String Permutations +** DONE Compute nth Fibonacci Number +** TODO Making Change +** TODO The Cake Thief +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +* Queues and stacks +** TODO Largest Stack +** TODO Implement A Queue With Two Stacks +** TODO Parenthesis Matching +** TODO Bracket Validator +* Linked lists +** DONE Delete Node +** TODO Does This Linked List Have A Cycle? +** TODO Reverse A Linked List +** TODO Kth to Last Node in a Singly-Linked List +** DONE Find Repeat, Space Edition BEAST MODE +* System design +** TODO URL Shortener +** TODO MillionGazillion +** TODO Find Duplicate Files +* General programming +** TODO Rectangular Love +** TODO Temperature Tracker +* Bit manipulation +** TODO Binary Numbers +** TODO The Stolen Breakfast Drone +* Combinatorics, probability, and other math +** TODO Which Appears Twice +** TODO Find in Ordered Set +** DONE In-Place Shuffle +** TODO Simulate 5-sided die +** TODO Simulate 7-sided die +** TODO Two Egg Problem +* JavaScript +** TODO JavaScript Scope +** TODO What's Wrong with This JavaScript? +* Coding interview tips +** TODO How The Coding Interview Works +** TODO General Coding Interview Advice +** TODO Impostor Syndrome +** TODO Why You Hit Dead Ends +** TODO Tips for Getting Unstuck +** TODO The 24 Hours Before Your Interview +** TODO Beating Behavioral Questions +** TODO Managing Your Interview Timeline diff --git a/users/wpcarro/scratch/deepmind/part_two/top-scores.py b/users/wpcarro/scratch/deepmind/part_two/top-scores.py new file mode 100644 index 000000000000..0ac349c1f880 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/top-scores.py @@ -0,0 +1,47 @@ +import unittest + + +def sort_scores(xs, highest_possible_score): + result = [] + buckets = [0] * highest_possible_score + + for x in xs: + buckets[x - 1] += 1 + + for i in range(highest_possible_score - 1, -1, -1): + if buckets[i] > 0: + for _ in range(buckets[i]): + result.append(i + 1) + + return result + + +# Tests +class Test(unittest.TestCase): + def test_no_scores(self): + actual = sort_scores([], 100) + expected = [] + self.assertEqual(actual, expected) + + def test_one_score(self): + actual = sort_scores([55], 100) + expected = [55] + self.assertEqual(actual, expected) + + def test_two_scores(self): + actual = sort_scores([30, 60], 100) + expected = [60, 30] + self.assertEqual(actual, expected) + + def test_many_scores(self): + actual = sort_scores([37, 89, 41, 65, 91, 53], 100) + expected = [91, 89, 65, 53, 41, 37] + self.assertEqual(actual, expected) + + def test_repeated_scores(self): + actual = sort_scores([20, 10, 30, 30, 10, 20], 100) + expected = [30, 30, 20, 20, 10, 10] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/top-scores.ts b/users/wpcarro/scratch/deepmind/part_two/top-scores.ts new file mode 100644 index 000000000000..79c10c883211 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/top-scores.ts @@ -0,0 +1,57 @@ +function sortScores(xs: Array<number>, highest: number): Array<number> { + const counts: Array<number> = []; + const result: Array<number> = []; + + // Initialize counts + for (let i = 0; i <= highest; i += 1) { + counts.push(0); + } + + for (let i = 0; i < xs.length; i += 1) { + counts[xs[i]] += 1; + } + + for (let i = highest; i >= 0; i -= 1) { + let count: number = counts[i]; + + for (let j = 0; j < count; j += 1) { + result.push(i); + } + } + + return result; +} + +// Tests +let desc = "no scores"; +let actual = sortScores([], 100); +let expected = []; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "one score"; +actual = sortScores([55], 100); +expected = [55]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "two scores"; +actual = sortScores([30, 60], 100); +expected = [60, 30]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "many scores"; +actual = sortScores([37, 89, 41, 65, 91, 53], 100); +expected = [91, 89, 65, 53, 41, 37]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "repeated scores"; +actual = sortScores([20, 10, 30, 30, 10, 20], 100); +expected = [30, 30, 20, 20, 10, 10]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/tsconfig.json b/users/wpcarro/scratch/deepmind/part_two/tsconfig.json new file mode 100644 index 000000000000..9b6918ca37d8 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "downlevelIteration": true, + "target": "es5", + "lib": ["es6", "dom"] + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/word-cloud.py b/users/wpcarro/scratch/deepmind/part_two/word-cloud.py new file mode 100644 index 000000000000..36ace8405f71 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/word-cloud.py @@ -0,0 +1,79 @@ +import unittest +import re +from collections import Counter + + +class WordCloudData(object): + def __init__(self, x): + x = x.replace('...', ' ').replace(' - ', ' ') + x = ''.join(c for c in x if c not in ',.!?;:') + self.words_to_counts = dict( + Counter(x.lower() for x in re.split(r'\s+', x))) + + +# Tests +class Test(unittest.TestCase): + def test_simple_sentence(self): + input = 'I like cake' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'i': 1, 'like': 1, 'cake': 1} + self.assertEqual(actual, expected) + + def test_longer_sentence(self): + input = 'Chocolate cake for dinner and pound cake for dessert' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = { + 'and': 1, + 'pound': 1, + 'for': 2, + 'dessert': 1, + 'chocolate': 1, + 'dinner': 1, + 'cake': 2, + } + self.assertEqual(actual, expected) + + def test_punctuation(self): + input = 'Strawberry short cake? Yum!' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'cake': 1, 'strawberry': 1, 'short': 1, 'yum': 1} + self.assertEqual(actual, expected) + + def test_hyphenated_words(self): + input = 'Dessert - mille-feuille cake' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'cake': 1, 'dessert': 1, 'mille-feuille': 1} + self.assertEqual(actual, expected) + + def test_ellipses_between_words(self): + input = 'Mmm...mmm...decisions...decisions' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'mmm': 2, 'decisions': 2} + self.assertEqual(actual, expected) + + def test_apostrophes(self): + input = "Allie's Bakery: Sasha's Cakes" + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {"bakery": 1, "cakes": 1, "allie's": 1, "sasha's": 1} + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/facebook/anglocize-int.py b/users/wpcarro/scratch/facebook/anglocize-int.py new file mode 100644 index 000000000000..a828230d0851 --- /dev/null +++ b/users/wpcarro/scratch/facebook/anglocize-int.py @@ -0,0 +1,71 @@ +THOUSAND = int(1e3) +MILLION = int(1e6) +BILLION = int(1e9) +TRILLION = int(1e12) + +facts = { + 1: "One", + 2: "Two", + 3: "Three", + 4: "Four", + 5: "Five", + 6: "Six", + 7: "Seven", + 8: "Eight", + 9: "Nine", + 10: "Ten", + 11: "Eleven", + 12: "Twelve", + 13: "Thirteen", + 14: "Fourteen", + 15: "Fifteen", + 16: "Sixteen", + 17: "Seventeen", + 18: "Eighteen", + 19: "Nineteen", + 20: "Twenty", + 30: "Thirty", + 40: "Forty", + 50: "Fifty", + 60: "Sixty", + 70: "Seventy", + 80: "Eighty", + 90: "Ninety", + 100: "Hundred", + THOUSAND: "Thousand", + MILLION: "Million", + BILLION: "Billion", + TRILLION: "Trillion", +} + +def anglocize(x): + # ones + if x >= 0 and x < 10: + pass + + # tens + elif x < 100: + pass + + # hundreds + elif x < THOUSAND: + pass + + # thousands + elif x < MILLION: + pass + + # millions + elif x < BILLION: + pass + + # billion + elif x < TRILLION: + pass + + # trillion + else: + pass + +x = 1234 +assert anglocize(x) == "One Thousand, Two Hundred Thirty Four" diff --git a/users/wpcarro/scratch/facebook/balanced-binary-tree.py b/users/wpcarro/scratch/facebook/balanced-binary-tree.py new file mode 100644 index 000000000000..afa9706f97ba --- /dev/null +++ b/users/wpcarro/scratch/facebook/balanced-binary-tree.py @@ -0,0 +1,70 @@ +from collections import deque + +class Node(object): + # __init__ :: T(A) + def __init__(self, value=None, left=None, right=None): + self.value = value + self.left = left + self.right = right + + # insert_left :: T(A) -> A -> T(A) + def insert_left(self, value): + self.left = Node(value) + return self.left + + # insert_right :: T(A) -> A -> T(A) + def insert_right(self, value): + self.right = Node(value) + return self.right + + # is_superbalanced :: T(A) -> Bool + def is_superbalanced(self): + xs = deque() + min_depth, max_depth = float('inf'), float('-inf') + xs.append((self, 0)) + while xs: + x, d = xs.popleft() + # Only redefine the depths at leaf nodes + if not x.left and not x.right: + min_depth, max_depth = min(min_depth, d), max(max_depth, d) + if x.left: + xs.append((x.left, d + 1)) + if x.right: + xs.append((x.right, d + 1)) + return max_depth - min_depth <= 1 + + # __repr__ :: T(A) -> String + def __repr__(self): + result = '' + xs = deque() + xs.append((self, 0)) + while xs: + node, indent = xs.popleft() + result += '{i}{x}\n'.format(i=' ' * indent, x=node.value) + if node.left: + xs.append((node.left, indent + 2)) + if node.right: + xs.append((node.right, indent + 2)) + return result + +# from_array :: List(A) -> T(A) +def from_array(values): + xs = deque() + root = Node() + xs.append(root) + for value in values: + node = xs.popleft() + node.value = value + node.left = Node() + xs.append(node.left) + node.right = Node() + xs.append(node.right) + return root + +x = from_array([1, 1, 1, 1, 1, 1, 1]) +print(x) +print(x.is_superbalanced()) + +x = Node(1, Node(2), Node(3)) +print(x) +print(x.is_superbalanced()) diff --git a/users/wpcarro/scratch/facebook/breakfast-generator.py b/users/wpcarro/scratch/facebook/breakfast-generator.py new file mode 100644 index 000000000000..df9b5015ad3a --- /dev/null +++ b/users/wpcarro/scratch/facebook/breakfast-generator.py @@ -0,0 +1,112 @@ +# After being inspired by... +# craftinginterpreters.com/representing-code.html +# ...I'm implementing the breakfast generator that the author describes +# therein. + +import random +import string + +# Breakfast + +def breakfast(): + fn = random.choice([ + lambda: " ".join([protein(), "with", breakfast(), "on the side"]), + lambda: protein(), + lambda: bread(), + ]) + return fn() + +def protein(): + fn = random.choice([ + lambda: " ".join([qualifier(), "crispy", "bacon"]), + lambda: "sausage", + lambda: " ".join([cooking_method(), "sausage"]), + ]) + return fn() + +def qualifier(): + fn = random.choice([ + lambda: "really", + lambda: "super", + lambda: " ".join(["really", qualifier()]), + ]) + return fn() + +def cooking_method(): + return random.choice([ + "scrambled", + "poached", + "fried", + ]) + +def bread(): + return random.choice([ + "toast", + "biscuits", + "English muffin", + ]) + +print(breakfast()) + +# Expression Language + +# Because Python is a strictly evaluated language any functions that are +# mutually recursive won't terminate and will overflow our stack. Therefore, any +# non-terminals expressed in an alternative are wrapped in lambdas as thunks. + +def expression(): + fn = random.choice([ + lambda: literal(), + lambda: binary(), + ]) + return fn() + +def literal(): + return str(random.randint(0, 100)) + +def binary(): + return " ".join([expression(), operator(), expression()]) + +def operator(): + return random.choice(["+", "*"]) + +print(expression()) + +# Lox + +def lox_expression(): + fn = random.choice([ + lambda: lox_literal(), + lambda: lox_unary(), + lambda: lox_binary(), + lambda: lox_grouping(), + ]) + return fn() + +def lox_literal(): + fn = random.choice([ + lambda: str(random.randint(0, 100)), + lambda: lox_string(), + lambda: random.choice(["true", "false"]), + lambda: "nil", + ]) + return fn() + +def lox_string(): + return "\"{}\"".format( + "".join(random.choice(string.ascii_lowercase) + for _ in range(random.randint(0, 25)))) + +def lox_grouping(): + return "(" + lox_expression() + ")" + +def lox_unary(): + return random.choice(["-", "!"]) + lox_expression() + +def lox_binary(): + return lox_expression() + lox_operator() + lox_expression() + +def lox_operator(): + return random.choice(["==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"]) + +print(lox_expression()) diff --git a/users/wpcarro/scratch/facebook/bst-checker.py b/users/wpcarro/scratch/facebook/bst-checker.py new file mode 100644 index 000000000000..7ef63a95315e --- /dev/null +++ b/users/wpcarro/scratch/facebook/bst-checker.py @@ -0,0 +1,49 @@ +from collections import deque + +class Node(object): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + def is_bst(self): + s = [] + s.append((float('-inf'), self, float('inf'))) + while s: + lo, node, hi = s.pop() + if lo <= node.value <= hi: + node.left and s.append((lo, node.left, node.value)) + node.right and s.append((node.value, node.right, hi)) + else: + return False + return True + + +x = Node( + 50, + Node( + 17, + Node( + 12, + Node(9), + Node(14), + ), + Node( + 23, + Node(19), + ), + ), + Node( + 72, + Node( + 54, + None, + Node(67) + ), + Node(76), + ), +) + + +assert x.is_bst() +print("Success!") diff --git a/users/wpcarro/scratch/facebook/cafe-order-checker.py b/users/wpcarro/scratch/facebook/cafe-order-checker.py new file mode 100644 index 000000000000..9d88a68069fd --- /dev/null +++ b/users/wpcarro/scratch/facebook/cafe-order-checker.py @@ -0,0 +1,19 @@ +def orders_are_sorted(take_out, dine_in, audit): + if len(take_out) + len(dine_in) != len(audit): + return False + + i, j = 0, 0 + for x in audit: + if i < len(take_out) and take_out[i] == x: + i += 1 + elif j < len(dine_in) and dine_in[j] == x: + j += 1 + else: + return False + return True + + +assert orders_are_sorted([1,3,5], [2,4,6], [1,2,4,3,6,5]) +assert not orders_are_sorted([1,3,5], [2,4,6], [1,2,4,5,6,3]) +assert orders_are_sorted([], [2,4,6], [2,4,6]) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/cake_thief.py b/users/wpcarro/scratch/facebook/cake_thief.py new file mode 100644 index 000000000000..90a2add06658 --- /dev/null +++ b/users/wpcarro/scratch/facebook/cake_thief.py @@ -0,0 +1,61 @@ +from math import floor + +def print_table(table): + print('\n-- TABLE --') + for row in range(len(table)): + x = '' + for col in range(len(table[row])): + x += ' ' + str(table[row][col]) + print(x) + +def leftover(capacity, kg): + n = floor(capacity / kg) + return n, capacity - (n * kg) + +def init_table(num_rows, num_cols): + table = [] + for _ in range(num_rows): + row = [] + for _ in range(num_cols): + row.append(0) + table.append(row) + return table + +def get(table, row, col): + if row < 0 or col < 0: + return 0 + return table[row][col] + +def max_haul(items, capacity): + table = init_table(len(items), capacity) + + for row in range(len(table)): + for col in range(len(table[row])): + curr_capacity = col + 1 + kg, val = items[row] + # A + a = get(table, row - 1, col) + # B + n, lo = leftover(curr_capacity, kg) + b = (val * n) + get(table, row - 1, lo - 1) + # commit + if kg > curr_capacity: + table[row][col] = a + else: + print(n, lo) + table[row][col] = max([a, b]) + print_table(table) + return table[-1][-1] + +# There are multiple variants of this problem: +# 1. We're allowed to take multiple of each item. +# 2. We can only take one of each item. +# 3. We can only take a fixed amount of each item. + +items = [(7,160), (3,90), (2,15)] +capacity = 20 +result = max_haul(items, capacity) +expected = None +print("Result: {} == Expected: {}".format(result, expected)) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/camping-knapsack.py b/users/wpcarro/scratch/facebook/camping-knapsack.py new file mode 100644 index 000000000000..add59ed409cd --- /dev/null +++ b/users/wpcarro/scratch/facebook/camping-knapsack.py @@ -0,0 +1,46 @@ +from utils import get, init_table, print_table + +def max_haul(capacity, items, names): + table = init_table(rows=len(items), cols=capacity, default=0) + items_table = init_table(rows=len(items), cols=capacity, default=[]) + for row in range(len(table)): + for col in range(len(table[row])): + kg, value = items[row] + curr_capacity = col + 1 + + if kg > curr_capacity: + a = 0 + else: + a = value + get(table, row - 1, curr_capacity - kg - 1) + b = get(table, row - 1, col) + + if a > b: + rest = get(items_table, row - 1, curr_capacity - kg - 1) + knapsack = [names.get(items[row])] + if rest: + knapsack += rest + else: + knapsack = get(items_table, row - 1, col) + + table[row][col] = max([a, b]) + items_table[row][col] = knapsack + print_table(table) + return items_table[-1][-1] + +water = (3, 10) +book = (1, 3) +food = (2, 9) +jacket = (2, 5) +camera = (1, 6) +items = [water, book, food, jacket, camera] +result = max_haul(6, items, { + water: 'water', + book: 'book', + food: 'food', + jacket: 'jacket', + camera: 'camera', +}) +expected = ['camera', 'food', 'water'] +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/coin.py b/users/wpcarro/scratch/facebook/coin.py new file mode 100644 index 000000000000..354e2dfb58b8 --- /dev/null +++ b/users/wpcarro/scratch/facebook/coin.py @@ -0,0 +1,50 @@ +def init_table(rows=0, cols=0, default=None): + table = [] + for _ in range(rows): + row = [] + for _ in range(cols): + row.append(default) + table.append(row) + return table + +def print_table(table): + result = '' + for row in range(len(table)): + x = '' + for col in range(len(table[row])): + x += str(table[row][col]) + ' ' + result += x + '\n' + print(result) + +def get(table, row, col): + if row < 0 or col < 0: + return 0 + else: + return table[row][col] + +def make_change(coins, amt): + table = init_table(rows=len(coins), cols=amt, default=0) + for row in range(len(table)): + for col in range(len(table[row])): + coin = coins[row] + curr_amt = col + 1 + pull_down = get(table, row - 1, col) + + if curr_amt < coin: + table[row][col] = pull_down + elif curr_amt == coin: + table[row][col] = pull_down + 1 + else: + leftover = get(table, row, curr_amt - coin - 1) + table[row][col] = pull_down + leftover + + print_table(table) + return table[-1][-1] + +# 1 2 3 4 +# 1 1 1 1 1 +# 2 1 1 2 2 +# 3 1 1 3 4 + +result = make_change([3,2,1], 4) +print(result) diff --git a/users/wpcarro/scratch/facebook/count-islands.py b/users/wpcarro/scratch/facebook/count-islands.py new file mode 100644 index 000000000000..b876319b2f7a --- /dev/null +++ b/users/wpcarro/scratch/facebook/count-islands.py @@ -0,0 +1,53 @@ +from collections import deque + +def maybe_queue(row, col, game, q, seen): + """ + Add coordinate, (`row`, `col`), to the queue, `q`, as long as it exists in + the map, `game`, and it is not already present in `seen`. + """ + if row >= 0 and row < len(game) and col >= 0 and col < len(game[0]): + if game[row][col] == 'L' and (row, col) not in seen: + q.append((row, col)) + seen.add((row, col)) + +def visit_island(row, col, game, seen): + """ + Starting at the coordinate, (`row`, `col`), in the map, `game`, visit all + surrounding tiles marked as land by adding them to the `seen` set. + """ + q = deque() + q.append((row, col)) + while q: + row, col = q.popleft() + maybe_queue(row - 1, col, game, q, seen) # UP + maybe_queue(row + 1, col, game, q, seen) # DOWN + maybe_queue(row, col - 1, game, q, seen) # LEFT + maybe_queue(row, col + 1, game, q, seen) # RIGHT + +def count_islands(game): + """ + Return the number of contiguous land tiles in the map, `game`. + """ + result = 0 + seen = set() + for row in range(len(game)): + for col in range(len(game[row])): + if game[row][col] == 'L' and (row, col) not in seen: + visit_island(row, col, game, seen) + result += 1 + return result + +################################################################################ +# Tests +################################################################################ + +game = [ + "LWLWWW", + "LLLWWW", + "WWWLLW", +] + +result = count_islands(game) +print(result) +assert result == 2 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/delete-node.py b/users/wpcarro/scratch/facebook/delete-node.py new file mode 100644 index 000000000000..4034449ef0cd --- /dev/null +++ b/users/wpcarro/scratch/facebook/delete-node.py @@ -0,0 +1,19 @@ +from linked_list import Node, from_list + +def delete(node): + if not node.next: + node.value = None + else: + node.value = node.next.value + node.next = node.next.next + +one = Node(1) +two = Node(2) +three = Node(3) + +one.next = two +two.next = three + +print(one) +delete(two) +print(one) diff --git a/users/wpcarro/scratch/facebook/dijkstras.py b/users/wpcarro/scratch/facebook/dijkstras.py new file mode 100644 index 000000000000..7031701994a7 --- /dev/null +++ b/users/wpcarro/scratch/facebook/dijkstras.py @@ -0,0 +1,38 @@ +from heapq import heappush, heappop +import random + +# Dijkstra's algorithm will traverse a directed graph with weighted edges. If +# the edges aren't weighted, we can pretend that each edges weighs 1. The +# algorithm will find the shortest path between points A and B. + +def dijkstra(a, b, graph): + h = [] + seen = set() + heappush(h, (0, a, [a], [])) + while h: + km, x, path, steps = heappop(h) + + if x == b: + for a, b, d in steps: + print("{} -> {} => {}".format(a, b, d)) + return path, km + + seen.add(x) + for c, dist in graph[x]: + if c not in seen: + heappush(h, (km + dist, c, path + [c], steps + [(x, c, dist)])) + return [], float('inf') + +graph = { + 1: [(3, 9), (2, 7), (6, 14)], + 2: [(1, 7), (3, 10), (4, 15)], + 3: [(1, 9), (6, 2), (4, 11), (2, 10)], + 4: [(5, 6), (2, 15), (3, 11)], + 5: [(4, 6), (6, 9)], + 6: [(5, 9), (3, 2), (1, 14)], +} + +beg = random.choice(list(graph.keys())) +end = random.choice(list(graph.keys())) +print("Searching for the shortest path from {} -> {}".format(beg, end)) +print(dijkstra(beg, end, graph)) diff --git a/users/wpcarro/scratch/facebook/edit-distance.py b/users/wpcarro/scratch/facebook/edit-distance.py new file mode 100644 index 000000000000..a5b744f30f27 --- /dev/null +++ b/users/wpcarro/scratch/facebook/edit-distance.py @@ -0,0 +1,47 @@ +def print_grid(grid): + result = [] + for row in grid: + result.append(" ".join(str(c) for c in row)) + return print("\n".join(result)) + +def edit_distance(a, b): + """ + Compute the "edit distance" to transform string `a` into string `b`. + """ + grid = [] + for row in range(len(a) + 1): + r = [] + for col in range(len(b) + 1): + r.append(0) + grid.append(r) + + # left-to-right + # populate grid[0][i] + for col in range(len(grid[0])): + grid[0][col] = col + + # top-to-bottom + # populate grid[i][0] + for row in range(len(grid)): + grid[row][0] = row + + for row in range(1, len(grid)): + for col in range(1, len(grid[row])): + # last characters are the same + if a[0:row][-1] == b[0:col][-1]: + grid[row][col] = grid[row - 1][col - 1] + else: + # substitution + s = 1 + grid[row - 1][col - 1] + # deletion + d = 1 + grid[row - 1][col] + # insertion + i = 1 + grid[row][col - 1] + grid[row][col] = min(s, d, i) + print_grid(grid) + return grid[-1][-1] + +result = edit_distance("pizza", "pisa") +print(result) +assert result == 2 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/evaluator.hs b/users/wpcarro/scratch/facebook/evaluator.hs new file mode 100644 index 000000000000..1ba46a754892 --- /dev/null +++ b/users/wpcarro/scratch/facebook/evaluator.hs @@ -0,0 +1,39 @@ +module Evaluator where + +data Token + = TokenInt Integer + | TokenAdd + | TokenMultiply + deriving (Eq, Show) + +newtype AST = AST [Token] + deriving (Eq, Show) + +tokens :: [Token] +tokens = + [ TokenInt 13 + , TokenAdd + , TokenInt 2 + , TokenMultiply + , TokenInt 4 + , TokenAdd + , TokenInt 7 + , TokenAdd + , TokenInt 3 + , TokenMultiply + , TokenInt 8 + ] + +-- expression -> addition ; +-- addition -> multiplication ( "+" multiplication )* ; +-- multiplication -> terminal ( "*" terminal )* ; +-- terminal -> NUMBER ; + +parseExpression :: [Token] -> ([Token], AST) +parseExpression tokens = do + lhs, rest = parseMultiplication tokens + +parseMulitplication :: [Token] -> ([Token], AST) + +main :: IO () +main = print $ parse tokens diff --git a/users/wpcarro/scratch/facebook/evaluator.py b/users/wpcarro/scratch/facebook/evaluator.py new file mode 100644 index 000000000000..14deb66a8f65 --- /dev/null +++ b/users/wpcarro/scratch/facebook/evaluator.py @@ -0,0 +1,234 @@ +# After stumbling through my first technical screen, I'm going to drill +# algorithms for implementing evaluators for a toy expression language: +# e.g. 2 + 13 * 3 + 5 * 2 +# +# As of now, I'm aware of a few algorithms for solving this: +# - DONE: Convert infix expression to Polish notation and evaluate the Polish +# notation. +# - DONE: Evaluate the tokens using two stacks and avoid converting it. +# - DONE: Create a tree of depth two to encode the operator precedence and +# evaluate that AST. +# - TODO: Convert the infix expression to a prefix expression +# - TODO: Write a recursive descent parser and evaluate the AST. + +operators = { + '*': 1, + '+': 0, +} + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current == ' ': + i += 1 + continue + elif current in operators.keys(): + result.append(current) + i += 1 + else: + i += 1 + while i < len(xs) and xs[i] in {str(n) for n in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +# Convert infix to postfix; evaluate postfix +# I believe this is known as the Shunting-Yards algorithm +def postfix(tokens): + result = [] + s = [] + for token in tokens: + if type(token) == int: + result.append(token) + else: + while s and operators[token] < operators[s[-1]]: + result.append(s.pop()) + s.append(token) + while s: + result.append(s.pop()) + return result + +def do_evaluate_with_polish_notation(tokens): + s = [] + for token in tokens: + if token == '*': + s.append(s.pop() * s.pop()) + elif token == '+': + s.append(s.pop() + s.pop()) + else: + s.append(token) + return s[-1] + +def evaluate_with_polish_notation(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + pn = postfix(tokens) + print("Postfix: {}".format(pn)) + result = do_evaluate_with_polish_notation(pn) + print("Result: {}".format(result)) + return result + +# Evaluate Tokens + +def apply_operator(op, a, b): + if op == '*': + return a * b + elif op == '+': + return a + b + +def do_evaluate_tokens(tokens): + vals = [] + ops = [] + for token in tokens: + if type(token) == int: + vals.append(token) + elif token == '*': + ops.append(token) + elif token == '+': + while ops and operators[token] < operators[ops[-1]]: + vals.append(apply_operator(ops.pop(), vals.pop(), vals.pop())) + ops.append(token) + else: + raise Exception("Unexpected token: {}".format(token)) + while ops: + vals.append(apply_operator(ops.pop(), vals.pop(), vals.pop())) + return vals[-1] + +def evaluate_tokens(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + result = do_evaluate_tokens(tokens) + print("Result: {}".format(result)) + return result + +# Ad Hoc Tree + +def parse(tokens): + result = [] + series = [] + for token in tokens: + if type(token) == int: + series.append(token) + elif token == '*': + continue + elif token == '+': + result.append(series) + series = [] + else: + raise Exception("Unexpected token: {}".format(token)) + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def do_evaluate_ad_hoc_tree(ast): + return sum([product(xs) for xs in ast]) + +def evaluate_ad_hoc_tree(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + ast = parse(tokens) + print("AST: {}".format(ast)) + result = do_evaluate_ad_hoc_tree(ast) + print("Result: {}".format(result)) + return result + +# Recursive Descent Parser + +# expression -> addition ; +# addition -> multiplication ( "+" multiplication )* ; +# multiplication -> terminal ( "*" terminal )* ; +# terminal -> NUMBER ; + +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + # mutations + def advance(self): + self.i += 1 + + def consume(self): + result = self.curr() + self.advance() + return result + + # predicates + def match(self, x): + if self.curr() == x: + self.advance() + return True + return False + + def tokens_available(self): + return self.i < len(self.tokens) + + # getters + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] if self.tokens_available() else None + + def next(self): + return self.tokens[self.i + 1] + +def parse_expression(tokens): + parser = Parser(tokens) + return parse_addition(parser) + +def parse_addition(parser): + result = parse_multiplication(parser) + while parser.match("+"): + op = parser.prev() + rhs = parse_multiplication(parser) + result = ["+", result, rhs] + return result + +def parse_multiplication(parser): + result = parse_terminal(parser) + while parser.match("*"): + op = parser.prev() + rhs = parse_terminal(parser) + result = ["*", result, rhs] + return result + +def parse_terminal(parser): + # If we reach here, the current token *must* be a number. + return parser.consume() + +def evaluate_ast(ast): + if type(ast) == int: + return ast + else: + op, lhs, rhs = ast[0], ast[1], ast[2] + return apply_operator(op, evaluate_ast(lhs), evaluate_ast(rhs)) + +def evaluate_recursive_descent(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + ast = parse_expression(tokens) + print("AST: {}".format(ast)) + result = evaluate_ast(ast) + return result + +methods = { + 'Polish Notation': evaluate_with_polish_notation, + 'Evaluate Tokens': evaluate_tokens, + 'Ad Hoc Tree': evaluate_ad_hoc_tree, + 'Recursive Descent': evaluate_recursive_descent, +} + +for name, fn in methods.items(): + expr = "13 + 2 * 4 + 7 + 3 * 8" + print("Evaluating \"{}\" using the \"{}\" method...".format(expr, name)) + assert fn(expr) == eval(expr) + print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py b/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py new file mode 100644 index 000000000000..e246415efd1f --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py @@ -0,0 +1,57 @@ +def advance(position, xs): + """ + Return the next element in `xs` pointed to by the current `position`. + """ + return xs[position - 1] + +def find_duplicate(xs): + """ + Find the duplicate integer in the list, `xs`. + """ + beg = xs[-1] + a = beg + b = advance(a, xs) + # Find the first element of the cycle + cycle_beg = None + while a != b: + cycle_beg = a + a = advance(a, xs) + b = advance(b, xs) + b = advance(b, xs) + # The duplicate element is the element before the `cycle_beg` + a = beg + result = None + while a != cycle_beg: + result = a + a = advance(a, xs) + return result + +def find_duplicate(xs): + """ + This is the solution that InterviewCake.com suggests. + """ + # find length of the cycle + beg = xs[-1] + a = beg + for _ in range(len(xs)): + a = advance(a, xs) + element = a + a = advance(a, xs) + n = 1 + while a != element: + a = advance(a, xs) + n += 1 + # find the first element in the cycle + a, b = beg, beg + for _ in range(n): + b = advance(b, xs) + while a != b: + a = advance(a, xs) + b = advance(b, xs) + return a + +xs = [2, 3, 1, 3] +result = find_duplicate(xs) +print(result) +assert result == 3 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py b/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py new file mode 100644 index 000000000000..7c491aef604e --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py @@ -0,0 +1,22 @@ +import random + +def find_duplicate(xs): + print(xs) + # entry point in our cycle is the duplicate + i = xs[0] + j = xs[xs[0]] + while i != j: + print(i, xs[i], j, xs[j]) + i = xs[i] + j = xs[xs[j]] + # detect cycle + j = 0 + while i != j: + i = xs[i] + j = xs[j] + return xs[i] + +n = random.randint(5, 10) +xs = [random.randint(0, n - 1) for _ in range(n)] +result = find_duplicate(xs) +print(xs, result) diff --git a/users/wpcarro/scratch/facebook/find-rotation-point.py b/users/wpcarro/scratch/facebook/find-rotation-point.py new file mode 100644 index 000000000000..3636be4d93b5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-rotation-point.py @@ -0,0 +1,47 @@ +from math import floor + +def find_rotation(xs): + if xs[0] < xs[-1]: + return xs[0] + beg, end = 0, len(xs) - 1 + found = False + count = 10 + while not found and count >= 0: + i = beg + floor((end - beg) / 2) + if xs[beg] < xs[i]: + beg = i + i = beg + floor((end - beg) / 2) + elif xs[beg] > xs[i]: + end = i + found = xs[i - 1] > xs[i] + count -= 1 + return xs[i] + + +xs = [(['ptolemaic', + 'retrograde', + 'supplant', + 'undulate', + 'xenoepist', + 'zebra', + 'asymptote', + 'babka', + 'banoffee', + 'engender', + 'karpatka', + 'othellolagkage', + ], "asymptote"), + (['asymptote', + 'babka', + 'banoffee', + 'engender', + 'karpatka', + 'othellolagkage', + ], "asymptote"), + ] + +for x, expected in xs: + result = find_rotation(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py b/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py new file mode 100644 index 000000000000..56032aa05c8c --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py @@ -0,0 +1,17 @@ +import random + +def find_duplicate(xs): + mini, maxi, acc = xs[0], xs[0], xs[0] + for i in range(1, len(xs)): + mini = min(mini, xs[i]) + maxi = max(maxi, xs[i]) + acc = acc ^ xs[i] + mask = mini + for i in range(mini + 1, maxi + 1): + mask = mask ^ i + return mask ^ acc + +xs = [5, 3, 4, 1, 5, 2] +print(xs) +result = find_duplicate(xs) +print(result) diff --git a/users/wpcarro/scratch/facebook/graph-coloring.py b/users/wpcarro/scratch/facebook/graph-coloring.py new file mode 100644 index 000000000000..e5b6d9c89332 --- /dev/null +++ b/users/wpcarro/scratch/facebook/graph-coloring.py @@ -0,0 +1,60 @@ +from collections import deque + +class Palette(object): + def __init__(self, n): + self.i = 0 + self.colors = list(range(n)) + + def get(self): + return self.colors[self.i] + + def advance(self): + self.i += 1 % len(self.colors) + +class GraphNode(object): + def __init__(self, label): + self.label = label + self.neighbors = set() + self.color = None + + def __repr__(self): + result = [] + xs = deque() + xs.append(self) + seen = set() + while xs: + node = xs.popleft() + result.append('{} ({})'.format(node.label, str(node.color))) + for c in node.neighbors: + if c.label not in seen: + xs.append(c) + seen.add(node.label) + return ', '.join(result) + +def color_graph(graph, d): + seen = set() + start = graph + xs = deque() + palette = Palette(d + 1) + xs.append((start, palette.get())) + while xs: + x, color = xs.popleft() + x.color = color + for c in x.neighbors: + if c.label not in seen: + palette.advance() + xs.append((c, palette.get())) + seen.add(x.label) + +a = GraphNode('a') +b = GraphNode('b') +c = GraphNode('c') + +a.neighbors.add(b) +b.neighbors.add(a) +b.neighbors.add(c) +c.neighbors.add(b) + +print(a) +color_graph(a, 3) +print(a) diff --git a/users/wpcarro/scratch/facebook/hard/binary-adder.py b/users/wpcarro/scratch/facebook/hard/binary-adder.py new file mode 100644 index 000000000000..f79a9f22b38b --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/binary-adder.py @@ -0,0 +1,22 @@ +import random + +def add(a, b): + """ + Return the sum of `a` and `b`. + """ + if b == 0: + return a + sum = a ^ b + carry = (a & b) << 1 + return add(sum, carry) + +################################################################################ +# Tests +################################################################################ + +for _ in range(10): + x, y = random.randint(0, 100), random.randint(0, 100) + print("{} + {} = {} == {}".format(x, y, x + y, add(x, y))) + assert add(x, y) == x + y + print("Pass!") +print("Success!") diff --git a/users/wpcarro/scratch/facebook/hard/fisher-yates.py b/users/wpcarro/scratch/facebook/hard/fisher-yates.py new file mode 100644 index 000000000000..200d1613ddcb --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/fisher-yates.py @@ -0,0 +1,7 @@ +import random + +def shuffle(xs): + n = len(xs) + for i in range(n): + j = random.randint(i, n - 1) + xs[i], xs[j] = xs[j], xs[i] diff --git a/users/wpcarro/scratch/facebook/hard/random-choice.py b/users/wpcarro/scratch/facebook/hard/random-choice.py new file mode 100644 index 000000000000..a5c6e4e6ee81 --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/random-choice.py @@ -0,0 +1,50 @@ +import random + +# This class of problems is known as "resevoir sampling". +def choose_a(m, xs): + """ + Randomly choose `m` elements from `xs`. + This algorithm runs in linear time with respect to the size of `xs`. + """ + result = [None] * m + for i in range(len(xs)): + j = random.randint(0, i) + if j < m: + result[j] = xs[i] + return result + +def choose_b(m, xs): + """ + This algorithm, which copies `xs`, which runs in linear time, and then + shuffles the copies, which also runs in linear time, achieves the same + result as `choose_a` and both run in linear time. + + `choose_a` is still preferable since it has a coefficient of one, while this + version has a coefficient of two because it copies + shuffles. + """ + ys = xs[:] + random.shuffle(ys) + return ys[:m] + +def choose_c(m, xs): + """ + This is one, possibly inefficient, way to randomly sample `m` elements from + `xs`. + """ + choices = set() + while len(choices) < m: + choices.add(random.randint(0, len(xs) - 1)) + return [xs[i] for i in choices] + +# ROYGBIV +xs = [ + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'indigo', + 'violet', +] +print(choose_b(3, xs)) +print(choose_c(3, xs)) diff --git a/users/wpcarro/scratch/facebook/hard/suffix-tree.py b/users/wpcarro/scratch/facebook/hard/suffix-tree.py new file mode 100644 index 000000000000..782678fb822c --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/suffix-tree.py @@ -0,0 +1,93 @@ +import random +from collections import deque + +def exists(pattern, tree): + """ + Return true if `pattern` exists in `tree`. + """ + if len(pattern) == 0: + return True + if len(pattern) == 1: + for branch in tree: + if branch[0] == pattern[0]: + return True + return False + for branch in tree: + if branch[0] == pattern[0]: + return exists(pattern[1:], branch[1]) + return False + +# Branch :: (Char, [Branch]) +# SuffixTree :: [Branch] + +def suffix_tree(xs): + """ + Create a suffix tree from the input string, `xs`. + """ + root = [] + for i in range(len(xs)): + curr = xs[i:] + parent = root + for c1 in curr: + grafted = False + for c2, children in parent: + if c1 == c2: + grafted = True + parent = children + if grafted: + continue + else: + children = [] + child = (c1, children) + parent.append(child) + parent = children + return root + +def suffix_tree(x): + """ + Creates a suffix from the input string, `x`. This implementation uses a + stack. + """ + result = [None, []] + q = deque() + for i in range(len(x)): + q.append((result, x[i:])) + while q: + parent, x = q.popleft() + s = [] + s.append((parent, x)) + while s: + parent, x = s.pop() + if not x: + continue + c, rest = x[0], x[1:] + grafted = False + for child in parent[1]: + if c == child[0]: + s.append((child, rest)) + grafted = True + if not grafted: + child = [c, []] + parent[1].append(child) + s.append((child, rest)) + return result[1] + +################################################################################ +# Tests +################################################################################ + +x = random.choice(["burrito", "pizza", "guacamole"]) +tree = suffix_tree(x) +for branch in tree: + print(branch) + +for _ in range(3): + n = len(x) + i, j = random.randint(0, n), random.randint(0, n) + pattern = x[min(i, j):max(i, j)] + print("Checking \"{}\" for \"{}\" ...".format(x, pattern)) + print("Result: {}".format(exists(pattern, tree))) + pattern = random.choice(["foo", "bar", "baz"]) + print("Checking \"{}\" for \"{}\" ...".format(x, pattern)) + print("Result: {}".format(exists(pattern, tree))) + print() diff --git a/users/wpcarro/scratch/facebook/heap.py b/users/wpcarro/scratch/facebook/heap.py new file mode 100644 index 000000000000..0c0dce91b4dd --- /dev/null +++ b/users/wpcarro/scratch/facebook/heap.py @@ -0,0 +1,30 @@ +from math import floor + +class Heap(object): + def __init__(self): + self.xs = [None] + self.i = 1 + + def __repr__(self): + return "[{}]".format(", ".join(str(x) for x in self.xs[1:])) + + def insert(self, x): + if len(self.xs) == 1: + self.xs.append(x) + self.i += 1 + return + self.xs.append(x) + i = self.i + while i != 1 and self.xs[floor(i / 2)] > self.xs[i]: + self.xs[floor(i / 2)], self.xs[i] = self.xs[i], self.xs[floor(i / 2)] + i = floor(i / 2) + self.i += 1 + + def root(self): + return self.xs[1] + +xs = Heap() +print(xs) +for x in [12, 15, 14, 21, 1, 10]: + xs.insert(x) + print(xs) diff --git a/users/wpcarro/scratch/facebook/highest-product-of-3.py b/users/wpcarro/scratch/facebook/highest-product-of-3.py new file mode 100644 index 000000000000..c237b8e52e2d --- /dev/null +++ b/users/wpcarro/scratch/facebook/highest-product-of-3.py @@ -0,0 +1,20 @@ +def hi_product(xs): + lowest_one, highest_one = min(xs[0], xs[1]), max(xs[0], xs[1]) + lowest_two, highest_two = xs[0] * xs[1], xs[0] * xs[1] + highest = float('-inf') + for x in xs[2:]: + highest = max(highest, highest_two * x, lowest_two * x) + lowest_one = min(lowest_one, x) + highest_one = max(highest_one, x) + lowest_two = min(lowest_two, highest_one * x, lowest_one * x) + highest_two = max(highest_two, highest_one * x, lowest_one * x) + return highest + +xs = [([-10,-10,1,3,2], 300), + ([1,10,-5,1,-100], 5000)] + +for x, expected in xs: + result = hi_product(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/infix-to-postfix.py b/users/wpcarro/scratch/facebook/infix-to-postfix.py new file mode 100644 index 000000000000..4c6d64494d95 --- /dev/null +++ b/users/wpcarro/scratch/facebook/infix-to-postfix.py @@ -0,0 +1,51 @@ +operators = { + '*': 1, + '+': 0, +} + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current in operators.keys(): + result.append(current) + i += 1 + continue + else: + i += 1 + while i < len(xs) and xs[i] in {str(n) for n in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +def postfix(xs): + result = [] + s = [] + for x in xs: + if x in operators.keys(): + while s and operators[s[-1]] >= operators[x]: + result.append(s.pop()) + s.append(x) + else: + result.append(x) + while s: + result.append(s.pop()) + return result + +def evaluate(xs): + s = [] + for x in xs: + print(s, x) + if x == '*': + s.append(s.pop() * s.pop()) + elif x == '+': + s.append(s.pop() + s.pop()) + else: + s.append(x) + print(s) + return s[-1] + + +print(evaluate(postfix(tokenize("12+3*10")))) diff --git a/users/wpcarro/scratch/facebook/inflight-entertainment.py b/users/wpcarro/scratch/facebook/inflight-entertainment.py new file mode 100644 index 000000000000..7ddea5350a4f --- /dev/null +++ b/users/wpcarro/scratch/facebook/inflight-entertainment.py @@ -0,0 +1,29 @@ +from random import choice +from utils import init_table + +def get(movie, seeking): + return any([movie in xs for xs in seeking.values()]) + +def set_complement(movie, seeking): + for duration, xs in seeking.items(): + seeking[duration].add(duration - movie) + +def choose_movies(tolerance, duration, movies): + seeking = {duration + i: set() for i in range(-1 * tolerance, tolerance + 1)} + for movie in movies: + if get(movie, seeking): + return movie, duration - movie + else: + set_complement(movie, seeking) + return None + +tolerance = 20 +duration = choice([1, 2, 3]) * choice([1, 2]) * choice([15, 30, 45]) +movies = [choice([1, 2, 3]) * choice([15, 30, 45]) for _ in range(10)] +print("Seeking two movies for a duration of [{}, {}] minutes".format(duration - tolerance, duration + tolerance)) +print(movies) +result = choose_movies(tolerance, duration, movies) +if result: + print("{} + {} = {}".format(result[0], result[1], duration)) +else: + print(":( We're sad because we couldn't find two movies for a {} minute flight".format(duration)) diff --git a/users/wpcarro/scratch/facebook/intersecting-linked-lists.py b/users/wpcarro/scratch/facebook/intersecting-linked-lists.py new file mode 100644 index 000000000000..80ac01dafd56 --- /dev/null +++ b/users/wpcarro/scratch/facebook/intersecting-linked-lists.py @@ -0,0 +1,34 @@ +class LinkedList(object): + def __init__(self, x): + self.val = x + self.next = None + + def __repr__(self): + if self.next: + return "{} -> {}".format(self.val, self.next) + return "{}".format(self.val) + +def find_intersection(a, b): + init_a, init_b = a, b + + while a != b: + a = a.next if a.next else init_b + b = b.next if b.next else init_a + + return a + +# make A... +e1 = LinkedList(5) +d1 = LinkedList(2); d1.next = e1 +c1 = LinkedList(3); c1.next = d1 # shared +b1 = LinkedList(1); b1.next = c1 # shared +a1 = LinkedList(4); a1.next = b1 # shared + +# make B... +c2 = LinkedList(1); c2.next = c1 +b2 = LinkedList(5); b2.next = c2 +a2 = LinkedList(6); a2.next = b2 + +print(a1) +print(a2) +print(find_intersection(a1, a2).val) diff --git a/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py b/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py new file mode 100644 index 000000000000..bbd52fa9c64c --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py @@ -0,0 +1,14 @@ +def is_valid(node): + """ + Return True if `node` is a valid binary search tree. + """ + s = [] + s.append((float('-inf'), node, float('inf'))) + while s: + lo, node, hi = s.pop() + if lo <= node.value <= hi: + node.lhs and s.append((lo, node.lhs, node.value)) + node.rhs and s.append((node.value, node.rhs, hi)) + else: + return False + return True diff --git a/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py b/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py new file mode 100644 index 000000000000..688c340b987b --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py @@ -0,0 +1,34 @@ +def valid(take_out, dine_in, served): + # edge case + if len(take_out) + len(dine_in) != len(served): + return False + i = 0 + j = 0 + k = 0 + while i < len(take_out) and j < len(dine_in): + if take_out[i] == served[k]: + i += 1 + elif dine_in[j] == served[k]: + j += 1 + else: + return False + k += 1 + # take out + while i < len(take_out): + if take_out[i] != served[k]: + return False + i += 1 + # dine in + while j < len(dine_in): + if dine_in[j] != served[k]: + return False + j += 1 + return True + +take_out = [17, 8, 24] +dine_in = [12, 19, 2] +served = [17, 8, 12, 19, 24, 2] +result = valid(take_out, dine_in, served) +print(result) +assert result +print("Success!") diff --git a/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py b/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py new file mode 100644 index 000000000000..523ecd959daf --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py @@ -0,0 +1,70 @@ +def contains_cycle(node): + """ + Return True if the linked-list, `node`, contains a cycle. + """ + if not node: + return False + a = node + b = node.next + while a != b: + a = a.next + if b and b.next and b.next.next: + b = b.next.next + else: + return False + return True + +################################################################################ +# Bonus +################################################################################ + +def first_node_in_cycle(node): + """ + Given that the linked-list, `node`, contains a cycle, return the first + element of that cycle. + """ + # enter the cycle + a = node + b = node.next + while a != b: + a = a.next + b = b.next.next + + # get the length of the cycle + beg = a + a = a.next + n = 1 + while a != beg: + a = a.next + n += 1 + + # run b n-steps ahead of a + a = node + b = node + for _ in range(n): + b = b.next + + # where they intersect is the answer + while a != b: + a = a.next + b = b.next + return a + +################################################################################ +# Tests +################################################################################ + +class Node(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + def __repr__(self): + return "Node({}) -> ...".format(self.value) + +d = Node('d') +c = Node('c', d) +b = Node('b', c) +a = Node('a', b) +d.next = b + +print(first_node_in_cycle(a)) diff --git a/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py b/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py new file mode 100644 index 000000000000..877bb218fdf5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py @@ -0,0 +1,30 @@ +def merge_sorted(xs, ys): + result = [] + i = 0 + j = 0 + while i < len(xs) and j < len(ys): + if xs[i] <= ys[j]: + result.append(xs[i]) + i += 1 + else: + result.append(ys[j]) + j += 1 + while i < len(xs): + result.append(xs[i]) + i += 1 + while j < len(xs): + result.append(ys[j]) + j += 1 + return result + +################################################################################ +# Tests +################################################################################ + +xs = [3, 4, 6, 10, 11, 15] +ys = [1, 5, 8, 12, 14, 19] +result = merge_sorted(xs, ys) +print(result) +assert len(result) == len(xs) + len(ys) +assert result == [1, 3, 4, 5, 6, 8, 10, 11, 12, 14, 15, 19] +print("Success!") diff --git a/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py b/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py new file mode 100644 index 000000000000..4629798cf711 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py @@ -0,0 +1,6 @@ +def fib(n): + cache = (0, 1) + for _ in range(n): + a, b = cache + cache = (b, a + b) + return cache[0] diff --git a/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py b/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py new file mode 100644 index 000000000000..ced3b336e0d9 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py @@ -0,0 +1,8 @@ +from collections import Counter + +def permutation_can_be_palindrome(x): + odd = 0 + for _, n in Counter(x): + if n % 0 != 0: + odd += 1 + return odd <= 1 diff --git a/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py b/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py new file mode 100644 index 000000000000..bfa465f98d7f --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py @@ -0,0 +1,17 @@ +class Queue(object): + def __init__(self): + self.lhs = [] + self.rhs = [] + + def enqueue(self, x): + self.lhs.append(x) + + def dequeue(self): + if self.rhs: + return self.rhs.pop() + while self.lhs: + self.rhs.append(self.lhs.pop()) + if self.rhs: + return self.rhs.pop() + else: + raise Exception("Attempting to remove an item from an empty queue") diff --git a/users/wpcarro/scratch/facebook/knapsack-faq.py b/users/wpcarro/scratch/facebook/knapsack-faq.py new file mode 100644 index 000000000000..ae04f5eb96c0 --- /dev/null +++ b/users/wpcarro/scratch/facebook/knapsack-faq.py @@ -0,0 +1,42 @@ +from utils import get, init_table, print_table + +# This problem has a few variants: +# - limited supply of each item +# - unlimited supply of each item +# - fractional amounts of each item (e.g. rice) + +def max_haul(capacity, items): + min_kg = min([kg for _, kg in items]) + max_kg = max([kg for _, kg in items]) + + cols = int(max_kg / min_kg) + fr_col_index = lambda index: min_kg * index + min_kg + to_col_index = lambda capacity: int((capacity - min_kg) * cols / max_kg) + + table = init_table(rows=len(items), cols=cols, default=0) + for row in range(len(table)): + for col in range(len(table[row])): + curr_capacity = fr_col_index(col) + value, kg = items[row] + + if kg > curr_capacity: + a = 0 + else: + a = value + get(table, row - 1, to_col_index(curr_capacity - kg)) + + b = get(table, row - 1, col) + table[row][col] = max([a, b]) + print_table(table) + return table[-1][-1] + +guitar = (1500, 1) +stereo = (3000, 4) +laptop = (2000, 3) +necklace = (2000, 0.5) +items = [necklace, guitar, stereo, laptop] +capacity = 4 +result = max_haul(capacity, items) +expected = 4000 +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py b/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py new file mode 100644 index 000000000000..dd258d924d10 --- /dev/null +++ b/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py @@ -0,0 +1,26 @@ +from linked_list import Node, from_list + +def kth_to_last_node(k, node): + one = node + two = node + for _ in range(k - 1): + if not one: + return None + one = one.next + while one.next: + one = one.next + two = two.next + return two.value + + +xs = from_list(["Angel Food", "Bundt", "Cheese", "Devil's Food", "Eccles"]) +result = kth_to_last_node(2, xs) +print(result) +assert result == "Devil's Food" +print("Success!") + +xs = from_list(["Angel Food", "Bundt"]) +result = kth_to_last_node(30, xs) +print(result) +assert result is None +print("Success!") diff --git a/users/wpcarro/scratch/facebook/language.py b/users/wpcarro/scratch/facebook/language.py new file mode 100644 index 000000000000..b57f469b49d2 --- /dev/null +++ b/users/wpcarro/scratch/facebook/language.py @@ -0,0 +1,70 @@ +import random + +# Write an evaluator for a small language: +# - operators: '+', '*' +# - operands: Integers +# +# E.g. evaluate("2+14*90+5*16") + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current in {'*', '+'}: + result.append(current) + i += 1 + continue + elif current == ' ': + i += 1 + continue + else: + i += 1 + while i < len(xs) and xs[i] in {str(x) for x in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +def ast(tokens): + result = [] + series = [] + for token in tokens: + if token == '+': + result.append(series) + series = [] + elif token == '*': + continue + else: + series.append(token) + if series: + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def evaluate(x): + tokens = tokenize(x) + tree = ast(tokens) + return sum([product(xs) for xs in tree]) + +n = 7 +operands = [random.randint(0, 100) for _ in range(n)] +operators = [random.choice(['+','*']) for _ in range(n - 1)] +expr = [] +for i in range(n - 1): + expr.append(operands[i]) + expr.append(operators[i]) +expr.append(operands[-1]) + +expr = ' '.join([str(x) for x in expr]) +print("Expression: {}".format(expr)) +print("Tokens: {}".format(tokenize(expr))) +print("AST: {}".format(ast(tokenize(expr)))) +print("Answer: {}".format(evaluate(expr))) +assert evaluate(expr) == eval(expr) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/language2.py b/users/wpcarro/scratch/facebook/language2.py new file mode 100644 index 000000000000..3aebd454833b --- /dev/null +++ b/users/wpcarro/scratch/facebook/language2.py @@ -0,0 +1,50 @@ + + + + + + + + + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + curr = xs[i] + if curr in {'*','+'}: + result.append(curr) + i += 1 + continue + i += 1 + while i < len(xs) and xs[i] in {str(x) for x in range(10)}: + curr += xs[i] + i += 1 + result.append(int(curr)) + return result + +def parse(tokens): + result = [] + series = [] + for token in tokens: + if token == '*': + continue + elif token == '+': + result.append(series) + series = [] + else: + series.append(token) + if series: + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def evaluate(tree): + return sum([product(xs) for xs in tree]) + +print(evaluate(parse(tokenize("2+30*8*9+10")))) diff --git a/users/wpcarro/scratch/facebook/largest-contiguous-sum.py b/users/wpcarro/scratch/facebook/largest-contiguous-sum.py new file mode 100644 index 000000000000..7761bf1c61df --- /dev/null +++ b/users/wpcarro/scratch/facebook/largest-contiguous-sum.py @@ -0,0 +1,15 @@ +def find_sum(xs): + result = float('-inf') + streak = 0 + for x in xs: + result = max(result, streak, x) + if streak + x <= 0: + streak = x + else: + streak += x + return result + + +x = [2,-8,3,-2,4,-10] +assert find_sum(x) == 5 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/largest-stack.py b/users/wpcarro/scratch/facebook/largest-stack.py new file mode 100644 index 000000000000..052db44153d4 --- /dev/null +++ b/users/wpcarro/scratch/facebook/largest-stack.py @@ -0,0 +1,49 @@ +from stack import Stack, from_list +from heapq import heapify, heappush, heappop +from random import shuffle + +class MaxStack(Stack): + def __init__(self): + self.max = Stack() + super().__init__() + + def __repr__(self): + return super().__repr__() + + def push(self, x): + super().push(x) + max = self.get_max() + if not max: + self.max.push(x) + else: + self.max.push(max if x < max else x) + + def pop(self): + self.max.pop() + return super().pop() + + def get_max(self): + return self.max.peek() + +xs = list(range(1, 11)) +shuffle(xs) +stack = MaxStack() +for x in xs: + stack.push(x) + +print(stack) +result = stack.get_max() +print(result) +assert result == 10 + +popped = stack.pop() +print("Popped: {}".format(popped)) +print(stack) +while popped != 10: + assert stack.get_max() == 10 + popped = stack.pop() + print("Popped: {}".format(popped)) + print(stack) + +assert stack.get_max() != 10 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/leetcode.org b/users/wpcarro/scratch/facebook/leetcode.org new file mode 100644 index 000000000000..6e915faf2913 --- /dev/null +++ b/users/wpcarro/scratch/facebook/leetcode.org @@ -0,0 +1,163 @@ +# This list is from: +# https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-100-LeetCode-Questions-to-Save-Your-Time-OaM1orEU +* Array +** DONE Two Sum + https://leetcode.com/problems/two-sum/ +** DONE Best Time to Buy and Sell Stock + https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ +** DONE Contains Duplicate + https://leetcode.com/problems/contains-duplicate/ +** DONE Product of Array Except Self + https://leetcode.com/problems/product-of-array-except-self/ +** DONE Maximum Subarray + https://leetcode.com/problems/maximum-subarray/ +** DONE Maximum Product Subarray + https://leetcode.com/problems/maximum-product-subarray/ +** DONE Find Minimum in Rotated Sorted Array + https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/ +** DONE Search in Rotated Sorted Array + https://leetcode.com/problems/search-in-rotated-sorted-array/ +** DONE 3Sum + https://leetcode.com/problems/3sum/ +** DONE Container With Most Water + https://leetcode.com/problems/container-with-most-water/ +* Binary +** DONE Sum of Two Integers + https://leetcode.com/problems/sum-of-two-integers/ +** DONE Number of 1 Bits + https://leetcode.com/problems/number-of-1-bits/ +** TODO Counting Bits + https://leetcode.com/problems/counting-bits/ +** DONE Missing Number + https://leetcode.com/problems/missing-number/ +** TODO Reverse Bits + https://leetcode.com/problems/reverse-bits/ +* Dynamic Programming +** DONE Climbing Stairs + https://leetcode.com/problems/climbing-stairs/ +** TODO Coin Change + https://leetcode.com/problems/coin-change/ +** TODO Longest Increasing Subsequence + https://leetcode.com/problems/longest-increasing-subsequence/ +** TODO Longest Common Subsequence +** DONE Word Break Problem + https://leetcode.com/problems/word-break/ +** TODO Combination Sum + https://leetcode.com/problems/combination-sum-iv/ +** TODO House Robber + https://leetcode.com/problems/house-robber/ +** TODO House Robber II + https://leetcode.com/problems/house-robber-ii/ +** TODO Decode Ways + https://leetcode.com/problems/decode-ways/ +** TODO Unique Paths + https://leetcode.com/problems/unique-paths/ +** TODO Jump Game + https://leetcode.com/problems/jump-game/ +* Graph +** DONE Clone Graph + https://leetcode.com/problems/clone-graph/ +** DONE Course Schedule + https://leetcode.com/problems/course-schedule/ +** TODO Pacific Atlantic Water Flow + https://leetcode.com/problems/pacific-atlantic-water-flow/ +** DONE Number of Islands + https://leetcode.com/problems/number-of-islands/ +** TODO Longest Consecutive Sequence + https://leetcode.com/problems/longest-consecutive-sequence/ +** TODO Alien Dictionary (Leetcode Premium) + https://leetcode.com/problems/alien-dictionary/ +** DONE Graph Valid Tree (Leetcode Premium) + https://leetcode.com/problems/graph-valid-tree/ +** DONE Number of Connected Components in an Undirected Graph (Leetcode Premium) + https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/ +* Interval +** TODO Insert Interval + https://leetcode.com/problems/insert-interval/ +** DONE Merge Intervals + https://leetcode.com/problems/merge-intervals/ +** TODO No Overlapping Intervals + https://leetcode.com/problems/non-overlapping-intervals/ +** DONE Meeting Rooms (Leetcode Premium) + https://leetcode.com/problems/meeting-rooms/ +** TODO Meeting Rooms II (Leetcode Premium) + https://leetcode.com/problems/meeting-rooms-ii/ +* Linked List +** DONE Reverse a Linked List + https://leetcode.com/problems/reverse-linked-list/ +** DONE Detect Cycle in a Linked List + https://leetcode.com/problems/linked-list-cycle/ +** DONE Merge Two Sorted Lists + https://leetcode.com/problems/merge-two-sorted-lists/ +** DONE Merge K Sorted Lists + https://leetcode.com/problems/merge-k-sorted-lists/ +** DONE Remove Nth Node From End Of List + https://leetcode.com/problems/remove-nth-node-from-end-of-list/ +** DONE Reorder List + https://leetcode.com/problems/reorder-list/ +* Matrix +** DONE Set Matrix Zeroes + https://leetcode.com/problems/set-matrix-zeroes/ +** DONE Spiral Matrix + https://leetcode.com/problems/spiral-matrix/ +** TODO Rotate Image + https://leetcode.com/problems/rotate-image/ +** DONE Word Search + https://leetcode.com/problems/word-search/ +* String +** TODO Longest Substring Without Repeating Characters + https://leetcode.com/problems/longest-substring-without-repeating-characters/ +** TODO Longest Repeating Character Replacement + https://leetcode.com/problems/longest-repeating-character-replacement/ +** TODO Minimum Window Substring + https://leetcode.com/problems/minimum-window-substring/ +** DONE Valid Anagram + https://leetcode.com/problems/valid-anagram/ +** DONE Group Anagrams + https://leetcode.com/problems/group-anagrams/ +** DONE Valid Parentheses + https://leetcode.com/problems/valid-parentheses/ +** DONE Valid Palindrome + https://leetcode.com/problems/valid-palindrome/ +** TODO Longest Palindromic Substring + https://leetcode.com/problems/longest-palindromic-substring/ +** TODO Palindromic Substrings + https://leetcode.com/problems/palindromic-substrings/ +** DONE Encode and Decode Strings (Leetcode Premium) + https://leetcode.com/problems/encode-and-decode-strings/ +* Tree +** DONE Maximum Depth of Binary Tree + https://leetcode.com/problems/maximum-depth-of-binary-tree/ +** DONE Same Tree + https://leetcode.com/problems/same-tree/ +** DONE Invert/Flip Binary Tree + https://leetcode.com/problems/invert-binary-tree/ +** DONE Binary Tree Maximum Path Sum + https://leetcode.com/problems/binary-tree-maximum-path-sum/ +** DONE Binary Tree Level Order Traversal + https://leetcode.com/problems/binary-tree-level-order-traversal/ +** DONE Serialize and Deserialize Binary Tree + https://leetcode.com/problems/serialize-and-deserialize-binary-tree/ +** DONE Subtree of Another Tree + https://leetcode.com/problems/subtree-of-another-tree/ +** DONE Construct Binary Tree from Preorder and Inorder Traversal + https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ +** DONE Validate Binary Search Tree + https://leetcode.com/problems/validate-binary-search-tree/ +** DONE Kth Smallest Element in a BST + https://leetcode.com/problems/kth-smallest-element-in-a-bst/ +** DONE Lowest Common Ancestor of BST + https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ +** DONE Implement Trie (Prefix Tree) + https://leetcode.com/problems/implement-trie-prefix-tree/ +** DONE Add and Search Word + https://leetcode.com/problems/add-and-search-word-data-structure-design/ +** DONE Word Search II + https://leetcode.com/problems/word-search-ii/ +* Heap +** DONE Merge K Sorted Lists + https://leetcode.com/problems/merge-k-sorted-lists/ +** DONE Top K Frequent Elements + https://leetcode.com/problems/top-k-frequent-elements/ +** DONE Find Median from Data Stream + https://leetcode.com/problems/find-median-from-data-stream/ diff --git a/users/wpcarro/scratch/facebook/linked-list-cycles.py b/users/wpcarro/scratch/facebook/linked-list-cycles.py new file mode 100644 index 000000000000..56f54d497808 --- /dev/null +++ b/users/wpcarro/scratch/facebook/linked-list-cycles.py @@ -0,0 +1,26 @@ +import random + +from linked_list import Node + +def contains_cycle(node): + one = node + two = node + while two.next and two.next.next: + one = one.next + two = two.next.next + if one == two: + return True + return False + +xs = Node(1, Node(2, Node(3))) +assert not contains_cycle(xs) +print("Success!") + +a = Node(1) +b = Node(2) +c = Node(3) +a.next = b +b.next = c +c.next = random.choice([a, b, c]) +assert contains_cycle(a) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/linked_list.py b/users/wpcarro/scratch/facebook/linked_list.py new file mode 100644 index 000000000000..1ae7061e8393 --- /dev/null +++ b/users/wpcarro/scratch/facebook/linked_list.py @@ -0,0 +1,22 @@ +class Node(object): + def __init__(self, value=None, next=None): + self.value = value + self.next = next + + def __repr__(self): + result = [] + node = self + while node: + result.append(str(node.value)) + node = node.next + return 'LinkedList({xs})'.format(xs=', '.join(result)) + +def from_list(xs): + head = Node(xs[0]) + node = head + for x in xs[1:]: + node.next = Node(x) + node = node.next + return head + +list = from_list(['A', 'B', 'C']) diff --git a/users/wpcarro/scratch/facebook/london-knapsack.py b/users/wpcarro/scratch/facebook/london-knapsack.py new file mode 100644 index 000000000000..a034fb49611d --- /dev/null +++ b/users/wpcarro/scratch/facebook/london-knapsack.py @@ -0,0 +1,42 @@ +from utils import get, init_table, print_table + +def optimal_itinerary(duration, items): + min_duration = min([duration for duration, _ in items]) + max_duration = max([duration for duration, _ in items]) + table = init_table(rows=len(items), cols=int(max_duration / min_duration), default=0) + to_index = lambda duration: int(duration / min_duration) - 1 + to_duration = lambda i: i * min_duration + min_duration + + for row in range(len(table)): + for col in range(len(table[row])): + curr_duration = to_duration(col) + duration, value = items[row] + if duration > curr_duration: + a = 0 + else: + a = value + get(table, row - 1, to_index(curr_duration - duration)) + b = get(table, row - 1, col) + table[row][col] = max([a, b]) + + print_table(table) + return table[-1][-1] + +# You're in London for two days, and you'd like to see the following +# attractions. How can you maximize your time spent in London? +westminster = (0.5, 7) +globe_theater = (0.5, 6) +national_gallery = (1, 9) +british_museum = (2, 9) +st_pauls_cathedral = (0.5, 8) +items = [ + westminster, + globe_theater, + national_gallery, + british_museum, + st_pauls_cathedral, +] +result = optimal_itinerary(2, items) +expected = 24 +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/longest-common-substring.py b/users/wpcarro/scratch/facebook/longest-common-substring.py new file mode 100644 index 000000000000..8a838db45d1e --- /dev/null +++ b/users/wpcarro/scratch/facebook/longest-common-substring.py @@ -0,0 +1,20 @@ +from utils import get, init_table, print_table + +def longest_common_substring(a, b): + """ + Computes the length of the longest string that's present in both `a` and + `b`. + """ + table = init_table(rows=len(b), cols=len(a), default=0) + for row in range(len(table)): + for col in range(len(table[row])): + if b[row] == a[col]: + table[row][col] = 1 + get(table, row - 1, col - 1) + return max([max(row) for row in table]) + +dictionary = ["fish", "vista"] +result = [longest_common_substring("hish", x) for x in dictionary] +expected = [3, 2] +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/merge-sorted-arrays.py b/users/wpcarro/scratch/facebook/merge-sorted-arrays.py new file mode 100644 index 000000000000..ae9377ad116b --- /dev/null +++ b/users/wpcarro/scratch/facebook/merge-sorted-arrays.py @@ -0,0 +1,44 @@ +def merge_sorted(xs, ys): + result = [] + i, j = 0, 0 + + while i < len(xs) and j < len(ys): + if xs[i] <= ys[j]: + result.append(xs[i]) + i += 1 + else: + result.append(ys[j]) + j += 1 + + while i < len(xs): + result.append(xs[i]) + i += 1 + + while j < len(ys): + result.append(ys[j]) + j += 1 + + return result + +# A +result = merge_sorted([3, 4, 6, 10, 11, 15], [1, 5, 8, 12, 14, 19]) +print(result) +assert result == [1, 3, 4, 5, 6, 8, 10, 11, 12, 14, 15, 19] + +# B +result = merge_sorted([], [1,2,3]) +print(result) +assert result == [1,2,3] + +# C +result = merge_sorted([1,2,3], []) +print(result) +assert result == [1,2,3] + +# D +result = merge_sorted([], []) +print(result) +assert result == [] + +# Wahoo! +print("Success!") diff --git a/users/wpcarro/scratch/facebook/merging-ranges.py b/users/wpcarro/scratch/facebook/merging-ranges.py new file mode 100644 index 000000000000..6da44572ee7e --- /dev/null +++ b/users/wpcarro/scratch/facebook/merging-ranges.py @@ -0,0 +1,23 @@ + +def merge(xs): + xs.sort() + result = xs[0:1] + for a, b in xs[1:]: + y, z = result[-1] + if a <= z: + result[-1] = (y, max(b, z)) + else: + result.append((a, b)) + return result + +inputs = [([(0,1),(3,5),(4,8),(10,12),(9,10)], [(0,1),(3,8),(9,12)]), + ([(1,2),(2,3)], [(1,3)]), + ([(1,5),(2,3)], [(1,5)]), + ([(1,10),(2,6),(3,5),(7,9)], [(1,10)]), + ] +for x, expected in inputs: + result = merge(x) + print(x) + print(result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/mesh-message.py b/users/wpcarro/scratch/facebook/mesh-message.py new file mode 100644 index 000000000000..8438b059d843 --- /dev/null +++ b/users/wpcarro/scratch/facebook/mesh-message.py @@ -0,0 +1,40 @@ +from heapq import heappush, heappop +import random + +def shortest_path(a, b, graph): + seen = set() + h = [] + heappush(h, (0, a, [a])) + while h: + km, x, path = heappop(h) + if x == b: + return path + for c in graph[x]: + if c not in seen: + heappush(h, (km + 1, c, path + [c])) + raise Exception("We were unable to find a path from {} to {}".format(a, b)) + +graph = { + 'Min' : ['William', 'Jayden', 'Omar'], + 'William' : ['Min', 'Noam'], + 'Jayden' : ['Min', 'Amelia', 'Ren', 'Noam'], + 'Ren' : ['Jayden', 'Omar'], + 'Amelia' : ['Jayden', 'Adam', 'Miguel'], + 'Adam' : ['Amelia', 'Miguel', 'Sofia', 'Lucas'], + 'Miguel' : ['Amelia', 'Adam', 'Liam', 'Nathan'], + 'Noam' : ['Nathan', 'Jayden', 'William'], + 'Omar' : ['Ren', 'Min', 'Scott'], + 'Liam' : ['Ren'], + 'Nathan' : ['Noam'], + 'Scott' : [], +} + +result = shortest_path('Jayden', 'Adam', graph) +print(result) +assert result == ['Jayden', 'Amelia', 'Adam'] +print('Success!') + +beg = random.choice(list(graph.keys())) +end = random.choice(list(graph.keys())) +print("Attempting to find the shortest path between {} and {}".format(beg, end)) +print(shortest_path(beg, end, graph)) diff --git a/users/wpcarro/scratch/facebook/moderate/decompress-xml.py b/users/wpcarro/scratch/facebook/moderate/decompress-xml.py new file mode 100644 index 000000000000..b22983ed7aff --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/decompress-xml.py @@ -0,0 +1,98 @@ +import string +from parser import Parser + +mapping = { + 1: "family", + 2: "person", + 3: "firstName", + 4: "lastName", + 5: "state", +} + +def parse_int(i, xs): + result = "" + while i < len(xs) and xs[i] in string.digits: + result += xs[i] + i += 1 + return i, int(result) + +def parse_string(i, xs): + result = "" + while xs[i+1] not in string.digits: + result += xs[i] + i += 1 + return i, result + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + if xs[i] in string.digits: + i, n = parse_int(i, xs) + result.append(n) + elif xs[i] in string.ascii_letters: + i, x = parse_string(i, xs) + result.append(x) + elif xs[i] == " ": + i += 1 + continue + return result + +def parse(xs): + parser = Parser(tokenize(xs)) + return parse_element(parser) + +# Element -> Tag Attribute* End Element* End ; +# Tag -> INTEGER ; +# Value -> STRING End ; +# Attribute -> Tag Value ; +# End -> 0 ; + +def parse_element(parser): + if type(parser.curr()) == str: + return parser.consume() + tag_id = parser.expect_predicate(lambda x: type(x) == int) + tag = mapping[tag_id] + attrs = parse_attrs(parser) + parser.expect([0]) + children = [] + while not parser.exhausted() and parser.curr() != 0: + children.append(parse_element(parser)) + parser.expect([0]) + return [tag, attrs, children] + +def parse_attrs(parser): + result = [] + while parser.curr() != 0: + tag_id = parser.expect_predicate(lambda x: type(x) == int) + tag = mapping[tag_id] + value = parser.consume() + result.append((tag, value)) + return result + +def stringify_xml(tree, indent=0): + if type(tree) == str: + return tree + result = "" + tag, attrs, children = tree + + str_attrs = [] + for k, v in attrs: + str_attrs.append("{}=\"{}\"".format(k, v)) + str_attrs = (" " if str_attrs else "") + " ".join(str_attrs) + + str_children = [] + for child in children: + str_children.append(" " * 2 * indent + stringify_xml(child, indent + 1)) + str_children = "\n".join(str_children) + + result += "{}<{}{}>\n{}{}\n{}</{}>".format( + " " * 2 * indent, tag, str_attrs, " " * 2 * indent, str_children, + " " * 2 * indent, tag) + return result + +x = "1 4 McDowell 5 CA 0 2 3 Gayle 0 Some Message 0 0" +print("Input: {}".format(x)) +print("Tokens: {}".format(tokenize(x))) +print("Parsed: {}".format(parse(x))) +print("{}".format(stringify_xml(parse(x)))) diff --git a/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py b/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py new file mode 100644 index 000000000000..69c2fc431296 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py @@ -0,0 +1,19 @@ +import random + +def find_pairs(xs, n): + """ + Return all pairs of integers in `xs` that sum to `n`. + """ + seeking = set() + result = set() + for x in xs: + if x in seeking: + result.add((n - x, x)) + else: + seeking.add(n - x) + return result + +xs = [random.randint(1, 10) for _ in range(10)] +n = random.randint(1, 10) + random.randint(1, 10) +print("Seeking all pairs in {} for {}...".format(xs, n)) +print(find_pairs(xs, n)) diff --git a/users/wpcarro/scratch/facebook/moderate/parser.py b/users/wpcarro/scratch/facebook/moderate/parser.py new file mode 100644 index 000000000000..57dfb058c037 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/parser.py @@ -0,0 +1,37 @@ +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def next(self): + return self.tokens[self.i + 1] + + def consume(self): + if not self.exhausted(): + self.i += 1 + return self.prev() + + def match(self, xs): + if not self.exhausted() and self.curr() in xs: + self.consume() + return True + return False + + def expect(self, xs): + if not self.match(xs): + raise Exception("Expected token \"{}\" but received \"{}\"".format(xs, self.curr())) + return self.prev() + + def expect_predicate(self, predicate): + if predicate(self.curr()): + return self.consume() + raise Exception("Expected token \"{}\" to pass predicate, but it did not".format(self.curr())) + + def exhausted(self): + return self.i >= len(self.tokens) diff --git a/users/wpcarro/scratch/facebook/moderate/rand7.py b/users/wpcarro/scratch/facebook/moderate/rand7.py new file mode 100644 index 000000000000..ed3a7cea80e5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/rand7.py @@ -0,0 +1,25 @@ +# Define a function, rand7, that generates a random number [0,7), using only +# rand5, which generates a random number [0,5). + +import random +from collections import Counter + +# Returns [0,4] +def rand5(): + return random.randint(0,4) + +# Return [0,6] +def rand7_a(): + return sum(rand5() for _ in range(7)) % 7 + +# Return [0,6] +def rand7_b(): + x = 5 * rand5() + rand5() + if x < 21: + return x % 7 + return rand7_b() + +c = Counter([rand7_a() for _ in range(100000)]) +print(c) +c = Counter([rand7_b() for _ in range(100000)]) +print(c) diff --git a/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py b/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py new file mode 100644 index 000000000000..342c29be6bdb --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py @@ -0,0 +1,99 @@ +import random + +def print_board(board): + result = [] + for row in range(len(board)): + r = [] + for col in range(len(board[row])): + cell = board[row][col] + if not cell: + r.append("-") + else: + r.append(cell) + result.append(" | ".join(r)) + print("\n---------\n".join(result)) + +def init_board(): + result = [] + for row in range(3): + r = [] + for col in range(3): + r.append(None) + result.append(r) + return result + +def check(board, player): + print_board(board) + print() + if player not in "XO": + raise Exception("Only checking the board for Xs or Os. You supplied {}".format(player)) + dn, ax, ddg, udg = "DOWN", "ACROSS", "DOWN_DIAGONAL", "UP_DIAGONAL" + ways = [ + [[dn, ax, ddg], [dn], [dn, udg]], + [[ax], [], []], + [[ax], [], []], + ] + for row in range(len(board)): + for col in range(len(board[row])): + if board[row][col] == player: + xs = ways[row][col] + for x in xs: + if x == dn: + if {player} == {board[row+1][col], board[row+2][col]}: + return True + if x == ax: + if {player} == {board[row][col+1], board[row][col+2]}: + return True + if x == ddg: + if {player} == {board[row+1][col+1], board[row+2][col+2]}: + return True + if x == udg: + if {player} == {board[row+1][col-1], board[row+2][col-2]}: + return True + return False + +def op(player): + return "X" if player == "O" else "O" + +dn_win = lambda p: [ + [op(p), p, None], + [op(p), p, None], + [None, p, None], +] + +ax_win = lambda p: [ + [p, p, p], + [op(p), op(p), None], + [None, None, None], +] + +ddg_win = lambda p: [ + [p, None, None], + [op(p), p, None], + [op(p), None, p], +] + +udg_win = lambda p: [ + [op(p), None, p], + [op(p), p, None], + [p, None, None], +] + +# Down +p = random.choice(["X", "O"]) +assert check(dn_win(p), p) == True +assert check(dn_win(p), op(p)) == False +# Across +p = random.choice(["X", "O"]) +assert check(ax_win(p), p) == True +assert check(ax_win(p), op(p)) == False +# Down Diagonally +p = random.choice(["X", "O"]) +assert check(ddg_win(p), p) == True +assert check(ddg_win(p), op(p)) == False +# Down Diagonally +p = random.choice(["X", "O"]) +assert check(udg_win(p), p) == True +assert check(udg_win(p), op(p)) == False +# Success +print("Tests pass!") diff --git a/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py b/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py new file mode 100644 index 000000000000..de7326b05822 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py @@ -0,0 +1,67 @@ +# Write a function that accepts an array of integers and returns the indices for +# the starting and ending integers that, if their elements were sorted, the +# entire array would be sorted. + +################################################################################ +# First Attempt +################################################################################ + +def unsorted_substring(xs): + ys = xs[:]; ys.sort() + m = 0 + while xs[m] == ys[m]: + m += 1 + if m >= len(xs): + return -1, -1 + n = len(xs) - 1 + while xs[n] == ys[n]: + n -= 1 + return m, n + +################################################################################ +# Second Attempt +################################################################################ + +def unsorted_substring_2(xs): + beg = 1 + while xs[beg - 1] <= xs[beg]: + beg += 1 + if beg >= len(xs): + return -1, -1 + end = len(xs) - 2 + while xs[end + 1] >= xs[end]: + end -= 1 + + min_mid = xs[beg] + max_mid = xs[beg] + i = beg + 1 + while i <= end: + min_mid = min(min_mid, xs[i]) + max_mid = max(max_mid, xs[i]) + i += 1 + + # beg -= 1 until max(lhs) <= min(mid) + while beg - 1 >= 0 and xs[beg - 1] >= min_mid: + beg -= 1 + + # end += 1 while max(mid) <= min(rhs) + while end + 1 < len(xs) and max_mid >= xs[end + 1]: + end += 1 + return beg, end + +################################################################################ +# Tests +################################################################################ + +xs = [ + [1,2,4,7,10,11,7,12,6,7,16,18,19], + [1,2,3,4], + [4,3,2,1], + [1,3,2,4], + [2,1,3,4], +] + +for x in xs: + print("Testing: {}".format(x)) + print("1) {}".format(unsorted_substring(x))) + print("2) {}".format(unsorted_substring_2(x))) diff --git a/users/wpcarro/scratch/facebook/move-zeroes-to-end.py b/users/wpcarro/scratch/facebook/move-zeroes-to-end.py new file mode 100644 index 000000000000..1535b5a9faac --- /dev/null +++ b/users/wpcarro/scratch/facebook/move-zeroes-to-end.py @@ -0,0 +1,62 @@ +from collections import deque + +def move_zeroes_to_end_quadratic(xs): + """ + This solution is suboptimal. It runs in quadratic time, and it uses constant + space. + """ + i = 0 + while i < len(xs) - 1: + if xs[i] == 0: + j = i + 1 + while j < len(xs) and xs[j] == 0: + j += 1 + if j >= len(xs): + break + xs[i], xs[j] = xs[j], xs[i] + i += 1 + +def move_zeroes_to_end_linear(xs): + """ + This solution is clever. It runs in linear time proportionate to the number + of elements in `xs`, and has linear space proportionate to the number of + consecutive zeroes in `xs`. + """ + q = deque() + for i in range(len(xs)): + if xs[i] == 0: + q.append(i) + else: + if q: + j = q.popleft() + xs[i], xs[j] = xs[j], xs[i] + q.append(i) + +def move_zeroes_to_end_linear_constant_space(xs): + """ + This is the optimal solution. It runs in linear time and uses constant + space. + """ + i = 0 + for j in range(len(xs)): + if xs[j] != 0: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + + +################################################################################ +# Tests +################################################################################ + +xss = [ + [1, 2, 0, 3, 4, 0, 0, 5, 0], + [0, 1, 2, 0, 3, 4], + [0, 0], +] + +f = move_zeroes_to_end_linear_constant_space + +for xs in xss: + print(xs) + f(xs) + print(xs) diff --git a/users/wpcarro/scratch/facebook/mst.py b/users/wpcarro/scratch/facebook/mst.py new file mode 100644 index 000000000000..81aa5cd48744 --- /dev/null +++ b/users/wpcarro/scratch/facebook/mst.py @@ -0,0 +1,71 @@ +from heapq import heappush, heappop +import random + +def to_vertex_list(graph): + result = {} + for a, b, kg in graph: + if a in result: + result[a].append((b, kg)) + else: + result[a] = [(b, kg)] + if b in result: + result[b].append((a, kg)) + else: + result[b] = [(a, kg)] + return result + +def mst(graph): + graph = to_vertex_list(graph) + beg = random.choice(list(graph.keys())) + h = [] + result = [] + seen = set() + for c, kg in graph[beg]: + heappush(h, (kg, beg, c)) + while h: + kg, beg, end = heappop(h) + # detect cycles + if end in seen: + continue + # use the edge + seen.add(beg) + seen.add(end) + result.append((beg, end)) + for c, kg in graph[end]: + heappush(h, (kg, end, c)) + return result + +graphs = [ + [ + ('A', 'B', 7), + ('A', 'D', 5), + ('B', 'D', 9), + ('E', 'D', 15), + ('F', 'D', 6), + ('F', 'G', 11), + ('F', 'E', 8), + ('G', 'E', 9), + ('C', 'E', 5), + ('B', 'E', 7), + ('B', 'C', 8), + ], + [ + ('A', 'B', 4), + ('A', 'C', 8), + ('B', 'C', 11), + ('B', 'E', 8), + ('C', 'D', 7), + ('C', 'F', 1), + ('D', 'E', 2), + ('D', 'F', 6), + ('E', 'G', 7), + ('E', 'H', 4), + ('F', 'H', 2), + ('G', 'H', 14), + ('G', 'I', 9), + ('H', 'I', 10), + ], +] + +for graph in graphs: + print(mst(graph)) diff --git a/users/wpcarro/scratch/facebook/n-queens.py b/users/wpcarro/scratch/facebook/n-queens.py new file mode 100644 index 000000000000..fc9326886cd8 --- /dev/null +++ b/users/wpcarro/scratch/facebook/n-queens.py @@ -0,0 +1,46 @@ +def print_board(board): + result = [] + for row in range(8): + r = [] + for col in range(8): + r.append("X" if col == board[row] else "-") + result.append(" ".join(r)) + print("\n".join(result)) + print() + +def can_place(board, row, col): + column_occupied = not any([board[i] == col for i in range(row)]) + + diagonals_clear = True + for r in range(row): + w = abs(col - board[r]) + h = abs(r - row) + if w == h: + diagonals_clear = False + break + + return all([column_occupied, diagonals_clear]) + +def init_board(): + board = [] + for row in range(8): + board.append(None) + return board + +def copy_board(board): + return board[:] + +def n_queens(): + do_n_queens(init_board(), 0, 0) + +def do_n_queens(board, row, col): + if row == 8: + print_board(board) + return + for i in range(col, 8): + if can_place(board, row, i): + copy = copy_board(board) + copy[row] = i + do_n_queens(copy, row + 1, 0) + +n_queens() diff --git a/users/wpcarro/scratch/facebook/nearby-words.py b/users/wpcarro/scratch/facebook/nearby-words.py new file mode 100644 index 000000000000..d2fc3cf5cfcc --- /dev/null +++ b/users/wpcarro/scratch/facebook/nearby-words.py @@ -0,0 +1,33 @@ +def nearby_chars(c): + keyboard = [ + "qwertyuiop", + "asdfghjkl", + "zxcvbnm", + ] + + for row in keyboard: + for i in range(len(row)): + if row[i] == c: + result = set() + if i + 1 < len(row): + result.add(row[i + 1]) + if i - 1 >= 0: + result.add(row[i - 1]) + return result + +def is_word(word): + words = { + "hello", + } + return word in words + +def nearby_words(x): + result = set() + for i in range(len(x)): + for c in nearby_chars(x[i]): + candidate = x[0:i] + c + x[i+1:] + if is_word(candidate): + result.add(candidate) + return result + +print(nearby_words('gello')) diff --git a/users/wpcarro/scratch/facebook/node.py b/users/wpcarro/scratch/facebook/node.py new file mode 100644 index 000000000000..4e24983af772 --- /dev/null +++ b/users/wpcarro/scratch/facebook/node.py @@ -0,0 +1,38 @@ +class Node(object): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + def insert_left(self, value): + self.left = Node(value) + return self.left + + def insert_right(self, value): + self.right = Node(value) + return self.right + +tree = Node( + 50, + Node( + 17, + Node( + 12, + Node(9), + Node(14), + ), + Node( + 23, + Node(19), + ), + ), + Node( + 72, + Node( + 54, + None, + Node(67) + ), + Node(76), + ), +) diff --git a/users/wpcarro/scratch/facebook/nth-fibonacci.py b/users/wpcarro/scratch/facebook/nth-fibonacci.py new file mode 100644 index 000000000000..f524067b3b44 --- /dev/null +++ b/users/wpcarro/scratch/facebook/nth-fibonacci.py @@ -0,0 +1,13 @@ +# 0, 1, 1, 2, 3, 5 +def fib(n): + if n < 0: + raise Exception("Need to supply an index that's >= 0. Not: {}".format(n)) + elif n in {0, 1}: + return n + state = [0, 1] + for i in range(1, n): + state[0], state[1] = state[1], state[0] + state[1] + return state[-1] + +for i in range(10): + print("fib({}) => {}".format(i, fib(i))) diff --git a/users/wpcarro/scratch/facebook/onsite.txt b/users/wpcarro/scratch/facebook/onsite.txt new file mode 100644 index 000000000000..b5242c4bd3a2 --- /dev/null +++ b/users/wpcarro/scratch/facebook/onsite.txt @@ -0,0 +1,22 @@ +** Behavior Interview ** +- Can I work in an unstructured environment? +- Do I have a growth mindset? +- How do I handle conflict? +- Am I empathic? +- Am I a self-starter? +- What is my communication style? +- Do I persevere? +- <forgot to write this one down> + +** Design Interview ** +- requirement gathering, problem exploring +- component analysis +- quantitative analysis +- trade-offs +- bottlenecks, weaknesses +- securing data (e.g. PII) + +Consider: +- pagination +- push/pull requests +- API design diff --git a/users/wpcarro/scratch/facebook/parsing/json.py b/users/wpcarro/scratch/facebook/parsing/json.py new file mode 100644 index 000000000000..3975e973fefa --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/json.py @@ -0,0 +1,121 @@ +from parser import Parser + +# As an exercise to stress-test my understanding of recursive descent parsers, +# I'm attempting to write a JSON parser without referencing any existing BNF +# descriptions of JSON or existing JSON parser implementations. +# +# I'm only parsing a subset of JSON: enough to parse `sample`. Here is the BNF +# that I wrote to describe my expected input: +# +# expression -> object +# object -> '{' ( STRING ':' expression ) ( ',' STRING ':' expression )* '}' +# | array +# array -> '[' expression ( ',' expression )* ']' +# | literal +# literal -> STRING | INT + +def tokenize(xs): + """ + Return a list of tokens from the string input, `xs`. + """ + result = [] + i = 0 + while i < len(xs): + # single characters + if xs[i] in ",{}:[]": + result.append(xs[i]) + i += 1 + # strings + elif xs[i] == "\"": + curr = xs[i] + i += 1 + while xs[i] != "\"": + curr += xs[i] + i += 1 + curr += xs[i] + result.append(curr) + i += 1 + # integers + elif xs[i] in "0123456789": + curr = xs[i] + i += 1 + while xs[i] in "0123456789": + curr += xs[i] + i += 1 + result.append(int(curr)) + # whitespace + elif xs[i] in {" ", "\n"}: + i += 1 + return result + +def parse_json(x): + """ + Attempt to parse the string, `x`, into JSON. + """ + tokens = tokenize(x) + return parse_object(Parser(tokens)) + +def parse_object(parser): + if parser.match(['{']): + key = parse_string(parser) + parser.expect([':']) + value = parse_object(parser) + result = [(key, value)] + while parser.match([',']): + key = parse_string(parser) + parser.match([':']) + value = parse_object(parser) + result.append((key, value)) + return result + return parse_array(parser) + +def parse_array(parser): + if parser.match(['[']): + if parser.match([']']): + return [] + result = [parse_object(parser)] + while parser.match([',']): + result.append(parse_object(parser)) + parser.expect([']']) + return result + else: + return parse_literal(parser) + +def parse_string(parser): + if parser.curr().startswith("\""): + return parser.consume() + else: + raise Exception("Unexpected token: {}".format(parser.curr())) + +def parse_literal(parser): + return parser.consume() + +sample = """ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML" + ] + }, + "GlossSee": "markup" + } + } + } + } +} +""" + +print(parse_json(sample)) diff --git a/users/wpcarro/scratch/facebook/parsing/parser.py b/users/wpcarro/scratch/facebook/parsing/parser.py new file mode 100644 index 000000000000..407bff61c980 --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/parser.py @@ -0,0 +1,28 @@ +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def consume(self): + if not self.exhausted(): + self.i += 1 + return self.prev() + + def match(self, xs): + if not self.exhausted() and self.curr() in xs: + self.consume() + return True + return False + + def expect(self, xs): + if not self.match(xs): + raise Exception("Expected token \"{}\" but received \"{}\"".format(xs, self.curr())) + + def exhausted(self): + return self.i >= len(self.tokens) diff --git a/users/wpcarro/scratch/facebook/parsing/regex.py b/users/wpcarro/scratch/facebook/parsing/regex.py new file mode 100644 index 000000000000..7fc2ef34e2ff --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/regex.py @@ -0,0 +1,184 @@ +# Writing a small proof-of-concept... +# - lexer +# - parser +# - compiler +# ...for regex. +# +# BNF +# expression -> ( char_class | CHAR ) quantifier? ( "|" expression )* +# char_class -> "[" CHAR+ "]" +# quantifier -> "?" | "*" | "+" | "{" INT? "," INT? "}" +# +# Of the numerous things I do not support, here are a few items of which I'm +# aware: +# - alternatives: (a|b) +# - capture groups: (ab)cd + +from parser import Parser +import string + +################################################################################ +# Top-Level API +################################################################################ + +def tokenize(xs): + """ + Transform `xs` into a list of tokens. + + Also: expand shorthand symbols using the following table: + - ? -> {0,1} + - * -> {0,} + - + -> {1,} + """ + result = [] + i = 0 + shorthand = { + "?": ["{", 0, ",", 1, "}"], + "*": ["{", 0, ",", "}"], + "+": ["{", 1, ",", "}"], + } + while i < len(xs): + if xs[i] in shorthand: + for c in shorthand[xs[i]]: + result.append(c) + i += 1 + elif xs[i] == "{": + result.append(xs[i]) + i += 1 + curr = "" + while xs[i] in string.digits: + curr += xs[i] + i += 1 + result.append(int(curr)) + assert xs[i] == "," + result.append(",") + i += 1 + curr = "" + while xs[i] in string.digits: + curr += xs[i] + i += 1 + result.append(int(curr)) + else: + result.append(xs[i]) + i += 1 + return result + +def parse(expr): + """ + Tokenize `expr` and convert it into a parse-tree. + """ + tokens = tokenize(expr) + return parse_tokens(tokens) + +def compile(xs): + """ + Transform `xs`, a parse-tree representing a regex, into a function that + accepts a string, and returns the substring that the regex matches. + """ + def fn(input): + match = "" + i = 0 + for x in xs: + matches, q = x[1], x[2] + lo, hi = q[1], q[2] + for j in range(lo): + if i < len(input) and input[i] in matches: + match += input[i] + i += 1 + else: + print("Failed to match {} with {}".format(input[i], matches)) + return None + if hi == float('inf'): + while i < len(input) and input[i] in matches: + match += input[i] + i += 1 + else: + for j in range(hi - lo): + if i < len(input) and input[i] in matches: + match += input[i] + i += 1 + return match + return fn + +################################################################################ +# Helper Functions +################################################################################ + +def parse_tokens(tokens): + result = [] + parser = Parser(tokens) + while not parser.exhausted(): + result.append(parse_expression(parser)) + return result + +def parse_expression(parser): + if parser.curr() == "[": + return parse_character_class(parser) + else: + return parse_character(parser) + +def parse_character_class(parser): + parser.expect("[") + beg = parser.consume() + parser.expect("-") + end = parser.consume() + parser.expect("]") + if parser.curr() == "{": + q = parse_quantifier(parser) + return char_class(xs=expand_range(beg, end), q=q) + +def parse_quantifier(parser): + parser.expect("{") + if parser.match([","]): + end = parser.consume() + parser.expect("}") + return quantifier(beg=0, end=end) + else: + beg = parser.consume() + parser.expect(",") + if parser.match(["}"]): + return quantifier(beg=beg) + else: + end = parser.consume() + parser.expect("}") + return quantifier(beg=beg, end=end) + +def parse_character(parser): + c = parser.consume() + q = None + if parser.curr() == "{": + q = parse_quantifier(parser) + return char_class(xs={c}, q=q) + +def char_class(xs=set(), q=None): + if not q: + q = quantifier(beg=1, end=1) + return ["CHARACTER_CLASS", xs, q] + +def expand_range(beg, end): + # TODO: Implement this + return {string.printable[i] + for i in range(string.printable.index(beg), + string.printable.index(end) + 1)} + +def quantifier(beg=0, end=float('inf')): + return ['QUANTIFIER', beg, end] + +################################################################################ +# Tests +################################################################################ + +xs = [ + ("[a-c]*[0-9]{2,3}", ["dog"]), + ("ca+t?", ["cat", "caaaat", "ca", "dog"]), +] + +for re, inputs in xs: + print("Regex: {}".format(re)) + print("Tokens: {}".format(tokenize(re))) + print("Parsed: {}".format(parse(re))) + print("\nTESTS") + for input in inputs: + print("Attempting to match \"{}\"...".format(input)) + parser = compile(parse(re)) + print("Result: \"{}\"\n".format(parser(input))) diff --git a/users/wpcarro/scratch/facebook/permutation-palindrome.py b/users/wpcarro/scratch/facebook/permutation-palindrome.py new file mode 100644 index 000000000000..30603578ffb3 --- /dev/null +++ b/users/wpcarro/scratch/facebook/permutation-palindrome.py @@ -0,0 +1,17 @@ +from collections import Counter + +def is_palindrome(x): + return len([count for _, count in Counter(x).items() if count % 2 == 1]) <= 1 + + +xs = [("civic", True), + ("ivicc", True), + ("civil", False), + ("livci", False)] + +for x, expected in xs: + result = is_palindrome(x) + print(x) + print(result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py b/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py new file mode 100644 index 000000000000..0c7b7cb5a0c4 --- /dev/null +++ b/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py @@ -0,0 +1,72 @@ +def compute_hash(x): + """ + Compute a unique fingerprint for the string input, `x`, as an integer using + the following equation: + + x[0] * P^0 + x[1] * P^1 + ... x[n-1] * P^(n-1) % M + + P and M are constants where P represents the next available prime number + that's GTE the number of unique characters you'll be hashing. In the case of + all lowercase characters, of which there are 26, the next available prime + number is 31. + """ + p = 31 + m = int(10e9) + 9 # large prime number + power = 0 + result = 0 + for c in x: + result += ord(c) * p**power + power += 1 + return result % m + +class HashTable(object): + def __init__(self, size): + """ + Create a hash table with `size` buckets. + """ + buckets = [] + for _ in range(size): + buckets.append([]) + self.xs = buckets + self.compute_hash = lambda k: compute_hash(k) % size + + def __repr__(self): + result = [] + for bucket in self.xs: + for entry in bucket: + result.append(entry) + return "HashTable({})".format(",".join(str(x) for x in result)) + + def get(self, key): + """ + Attempt to retrieve value stored under `key`. + """ + h = self.compute_hash(key) + for k, v in self.xs[h]: + if k == key: + return v + return None + + def put(self, key, val): + """ + Set `key` to `val`; update value at `key` if it already exists. + """ + h = self.compute_hash(key) + for i in range(len(self.xs[h])): + # Update entry if the key exists... + if self.xs[h][i][0] == key: + self.xs[h][i] = (key, val) + return None + # ...create a new entry otherwise + self.xs[h].append((key, val)) + + def delete(self, key): + """ + Remove entry `key` from the hash table. + """ + h = self.compute_hash(key) + for i in range(len(self.xs[h])): + k, v = self.xs[h][i] + if k == key: + self.xs[h].remove((k, v)) + return diff --git a/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py b/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py new file mode 100644 index 000000000000..d381386b6257 --- /dev/null +++ b/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py @@ -0,0 +1,33 @@ +from random import randint +from math import floor + +# loop {forwards, backwards, up, down} +# through a table of values, [[a]]. + +def product(xs): + n = len(xs) + lhs = [1] * (n + 1) + for i in range(1, n): + lhs[i] = lhs[i - 1] * xs[i - 1] + rhs = [1] * (n + 1) + for i in range(n - 1, 0, -1): + rhs[i] = rhs[i + 1] * xs[i] + result = [] + for i in range(n): + result.append(lhs[i] * rhs[i + 1]) + return result + +def computed_expected(xs): + product = 1 + for x in xs: + product *= x + return [floor(product / x) for x in xs] + +xs = [randint(1, 10) for _ in range(5)] +expected = computed_expected(xs) +result = product(xs) +print(xs, result, expected) +assert result == expected +print("Success!") + +print(product([2, 4, 3, 10, 5])) diff --git a/users/wpcarro/scratch/facebook/queue-two-stacks.py b/users/wpcarro/scratch/facebook/queue-two-stacks.py new file mode 100644 index 000000000000..a71abeb00552 --- /dev/null +++ b/users/wpcarro/scratch/facebook/queue-two-stacks.py @@ -0,0 +1,20 @@ +from stack import Stack + +class Queue(object): + def __init__(self): + self.lhs = Stack() + self.rhs = Stack() + + def enqueue(self, x): + self.rhs.push(x) + + def dequeue(self, x): + y = self.rhs.pop() + while y: + self.lhs.push(y) + y = self.rhs.pop() + result = self.lhs.pop() + y = self.lhs.pop() + while y: + self.rhs.push(y) + return result diff --git a/users/wpcarro/scratch/facebook/rabin-karp.py b/users/wpcarro/scratch/facebook/rabin-karp.py new file mode 100644 index 000000000000..53a47b278333 --- /dev/null +++ b/users/wpcarro/scratch/facebook/rabin-karp.py @@ -0,0 +1,27 @@ +def substring_exists(corpus, pattern): + """ + Return True if `pattern` appears in `corpus`. + + This function runs in O(m) time where n is equal to the length of + `corpus`. To improve the efficiency of this algorithm, use a hashing + function the reduces the number of collisions, which will consequently + reduce the number of string-to-string, linear comparisons. + """ + m, n = len(corpus), len(pattern) + a = sum(ord(c) for c in corpus[0:n]) + b = sum(ord(c) for c in pattern) + + # (clumsily) prevent an off-by-one error... + if a == b and corpus[0:n] == pattern: + return True + + for i in range(1, m - n): + # Update the hash of corpus by subtracting the hash of the character + # that is sliding out of view and adding the hash of the character that + # is sliding into view. + a = a - ord(corpus[i - 1]) + ord(corpus[i + n - 1]) + # Integer comparison in O(0) time followed by string comparison in O(m) + # time. + if a == b and corpus[i:i + n] == pattern: + return True + return False diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py new file mode 100644 index 000000000000..03b2de015dfe --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py @@ -0,0 +1,33 @@ +from math import floor + +def find_magic_index_brute(xs): + for i in range(len(xs)): + if xs[i] == i: + return i + return -1 + +def mid(lo, hi): + return lo + floor((hi - lo) / 2) + +def find_magic_index(xs): + lo, hi = 0, len(xs) - 1 + return do_find_magic_index(xs, 0, len(xs) - 1) + +def do_find_magic_index(xs, lo, hi): + pass + +xss = [ + [], + [-1,0,2,4,5,6], + [1,1,1,1,1,5], + [-2,-2,-2,-2,4], + [1,2,3,4,5], +] + +for xs in xss: + print(xs) + a = find_magic_index_brute(xs) + b = find_magic_index(xs) + print(a, b) + assert a == b + print("Success!") diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py new file mode 100644 index 000000000000..30c95a66c319 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py @@ -0,0 +1,56 @@ +# Given an infinite supply of: +# - quarters +# - dimes +# - nickels +# - pennies +# Write a function to count the number of ways to make change of n. + +def get(table, row, col): + """ + Defensively get cell `row`, `col` from `table`. + """ + if row < 0 or row >= len(table): + return 0 + if col < 0 or col >= len(table[0]): + return 0 + return table[row][col] + +def print_table(table): + print('\n'.join([ + ','.join([str(col) for col in table[row]]) + for row in range(len(table))])) + +def init_table(rows=0, cols=0, default=0): + result = [] + for row in range(rows): + r = [] + for col in range(cols): + r.append(default) + result.append(r) + return result + +def make_change(n): + coins = [1, 5, 10, 25] + table = init_table(rows=len(coins), cols=n) + + for row in range(len(table)): + for col in range(len(table[row])): + curr_coin = coins[row] + curr_n = col + 1 + # a + a = get(table, row - 1, col) + # b + b = get(table, row, curr_n - curr_coin - 1) + # c + c = 1 if curr_coin <= curr_n else 0 + # commit + if curr_coin == curr_n: + table[row][col] = a + c + else: + table[row][col] = a + b * c + # debug + print_table(table) + print() + return table[-1][-1] + +print(make_change(7)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py new file mode 100644 index 000000000000..e9e7f6a9c159 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py @@ -0,0 +1,36 @@ +from collection import deque + +def fill(point, canvas, color): + if x not in canvas: + return + elif y not in canvas[x]: + return + + x, y = point + if canvas[y][x] == color: + return + canvas[y][x] = color + fill((x + 1, y), canvas, color) + fill((x - 1, y), canvas, color) + fill((x, y + 1), canvas, color) + fill((x, y - 1), canvas, color) + +def fill_bfs(point, canvas, color): + x, y = point + if x not in canvas: + return None + if y not in canvas[x]: + return None + xs = deque() + xs.append((x, y)) + while xs: + x, y = xs.popleft() + canvas[y][x] = color + for x2, y2 in [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]: + if x2 not in canvas: + continue + elif y2 not in canvas[x2]: + continue + if canvas[y2][x2] != color: + xs.append((x2, y2)) + return None diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py new file mode 100644 index 000000000000..f406d64e657f --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py @@ -0,0 +1,114 @@ +# BNF +# expression -> bool ( ( '|' | '&' | '^' ) bool )* +# bool -> '0' | '1' + +def tokenize(xs): + result = [] + for c in xs: + if c == '0': + result.append(0) + elif c == '1': + result.append(1) + elif c in "&|^": + result.append(c) + else: + raise Exception("Unexpected token, \"{}\"".format(c)) + return result + +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def match(self, xs): + if self.exhausted(): + return False + if (self.curr() in xs): + self.consume() + return True + return False + + def consume(self): + result = self.curr() + self.i += 1 + return result + + def exhausted(self): + return self.i >= len(self.tokens) + +def recursive_descent(tokens): + parser = Parser(tokens) + return parse_expression(parser) + +def parse_expression(parser): + lhs = parse_bool(parser) + while parser.match(['|', '&', '^']): + op = parser.prev() + rhs = parse_expression(parser) + lhs = [op, lhs, rhs] + return lhs + +def parse_bool(parser): + if parser.curr() == 0: + parser.consume() + return False + elif parser.curr() == 1: + parser.consume() + return True + else: + raise Exception("Unexpected token: {}".format(parser.curr())) + +def f(expr, result): + tokens = tokenize(expr) + tree = recursive_descent(tokens) + return do_f(tree, result) + +def do_f(tree, result): + if type(tree) == bool: + if tree == result: + return 1 + else: + return 0 + + op, lhs, rhs = tree[0], tree[1], tree[2] + truth_tables = { + True: { + '|': [ + (True, True), + (True, False), + (False, True), + ], + '&': [ + (True, True), + ], + '^': [ + (True, False), + (False, True), + ], + }, + False: { + '|': [ + (False, False), + ], + '&': [ + (False, False), + (True, False), + (False, True), + ], + '^': [ + (True, True), + (False, False), + ], + } + } + + return sum([do_f(lhs, x) * do_f(rhs, y) for x, y in truth_tables[result][op]]) + +print(f("1^0|0|1", False)) +print(f("1|0|1|1", False)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py new file mode 100644 index 000000000000..e23972d4186d --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py @@ -0,0 +1,13 @@ +def char_and_rest(i, xs): + return xs[i], xs[:i] + xs[i+1:] + +# perms :: String -> [String] +def perms(xs): + if len(xs) == 1: + return [xs] + result = [] + for c, rest in [char_and_rest(i, xs) for i in range(len(xs))]: + result += [c + perm for perm in perms(rest)] + return result + +print(perms("cat")) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py new file mode 100644 index 000000000000..9ccc08526a99 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py @@ -0,0 +1,28 @@ +import random + +def factorial(n): + result = 1 + for i in range(1, n + 1): + result *= i + return result + +def travel(a, b): + if a == b: + return 1 + + ax, ay = a + bx, by = b + if ax > bx or ay > by: + return 0 + + return sum([travel((ax + 1, ay), b), travel((ax, ay + 1), b)]) + +def travel_compute(a, b): + bx, by = b + return int(factorial(bx + by) / (factorial(bx) * factorial(by))) + +a = (0, 0) +b = (random.randint(1, 10), random.randint(1, 10)) +print("Travelling to {}, {}".format(b[0], b[1])) +print(travel(a, b)) +print(travel_compute(a, b)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py new file mode 100644 index 000000000000..5eb4a8560674 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py @@ -0,0 +1 @@ +# accidentally deleted my solution... TBI (again) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py new file mode 100644 index 000000000000..a6d26aa85055 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py @@ -0,0 +1,41 @@ +# take-aways: +# - Use integers as lists of boolean values +# - Use 1 << n to compute 2^n where n = len(xs) + +def set_from_int(xs, n): + result = [] + for i in range(len(xs)): + if n & (1 << i) != 0: + result.append(xs[i]) + return result + +# subsets :: Set a -> List (Set a) +def subsets(xs): + n = len(xs) + return [set_from_int(xs, i) for i in range(1 << n)] + +# 0 1 2 +# 0 N Y Y +# 1 _ N Y +# 2 _ _ N + +# For my interview, be able to compute *permutations* and *combinations* + +# This differs from permutations because this is about finding combinations... +# +# bottom-up +# 0 => { } +# 1 => {3} {4} {3} +# 2 => {5,4} {5,3} {4,3} + +xs = [ + ([], [[]]), + ([5], [[], [5]]), + ([5,4], [[],[5],[4],[5,4]]), +] + +for x, expected in xs: + result = subsets(x) + print("subsets({}) => {} == {}".format(x, result, expected)) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py new file mode 100644 index 000000000000..56f2c0b27456 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py @@ -0,0 +1,50 @@ +def valid_parens(n): + if n == 0: + return [] + if n == 1: + return ["()"] + + result = set() + for x in valid_parens(n - 1): + result.add("({})".format(x)) + result.add("(){}".format(x)) + result.add("{}()".format(x)) + return result + +def valid_parens_efficient(n): + result = [] + curr = [''] * n**2 + do_valid_parens_efficient(result, curr, 0, n, n) + return result + +def do_valid_parens_efficient(result, curr, i, lhs, rhs): + if lhs == 0 and rhs == 0: + result.append(''.join(curr)) + else: + if lhs > 0: + curr[i] = '(' + do_valid_parens_efficient(result, curr, i + 1, lhs - 1, rhs) + if rhs > lhs: + curr[i] = ')' + do_valid_parens_efficient(result, curr, i + 1, lhs, rhs - 1) + +# Avoids recursion by using either a stack or a queue. I think this version is +# easier to understand. +def valid_parens_efficient_2(n): + result = [] + xs = [] + xs.append(('', n, n)) + while xs: + curr, lhs, rhs = xs.pop() + print(curr) + if lhs == 0 and rhs == 0: + result.append(''.join(curr)) + if lhs > 0: + xs.append((curr + '(', lhs - 1, rhs)) + if rhs > lhs: + xs.append((curr + ')', lhs, rhs - 1)) + return result + +# print(valid_parens(4)) +print(valid_parens_efficient(3)) +print(valid_parens_efficient_2(3)) diff --git a/users/wpcarro/scratch/facebook/recursive-string-permutations.py b/users/wpcarro/scratch/facebook/recursive-string-permutations.py new file mode 100644 index 000000000000..e4c61eff9f62 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursive-string-permutations.py @@ -0,0 +1,19 @@ +# permutations: no repeat characters + +def char_and_rest(i, xs): + return xs[i], xs[0:i] + xs[i + 1:] + +def permutations(xs): + if len(xs) == 1: + return [xs] + result = [] + for c, rest in [char_and_rest(i, xs) for i in range(len(xs))]: + result += [c + perm for perm in permutations(rest)] + return result + +expected = ["cat", "cta", "act", "atc", "tca", "tac"] +result = permutations("cat") +print(result, expected) +assert len(result) == len(expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/reverse-linked-list.py b/users/wpcarro/scratch/facebook/reverse-linked-list.py new file mode 100644 index 000000000000..820726733f9d --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-linked-list.py @@ -0,0 +1,25 @@ +from linked_list import Node + +def reverse(node): + prev, curr, next = None, node, node.next + + while curr: + curr.next = prev + prev = curr + curr = next + next = curr.next if curr else None + return prev + +one = Node(1) +two = Node(2) +three = Node(3) +one.next = two +two.next = three + +print(one) +result = reverse(one) +print(result) +assert all([result == three, + three.next == two, + two.next == one]) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/reverse-string-in-place.py b/users/wpcarro/scratch/facebook/reverse-string-in-place.py new file mode 100644 index 000000000000..72cd6c27a370 --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-string-in-place.py @@ -0,0 +1,14 @@ +# reverse :: [Char] -> () +def reverse(xs): + i = 0 + j = len(xs) - 1 + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + +xs = [list("testing"), list("a"), list("to")] +for x in xs: + print(x) + reverse(x) + print(x) diff --git a/users/wpcarro/scratch/facebook/reverse-words.py b/users/wpcarro/scratch/facebook/reverse-words.py new file mode 100644 index 000000000000..5a38b828a346 --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-words.py @@ -0,0 +1,8 @@ +# reverse_word :: [Char] -> () +def reverse_words(x): + pass + +x = list("This is a test") +print(''.join(x)) +reverse_words(x) +print(''.join(result)) diff --git a/users/wpcarro/scratch/facebook/scratch.py b/users/wpcarro/scratch/facebook/scratch.py new file mode 100644 index 000000000000..e772d758473d --- /dev/null +++ b/users/wpcarro/scratch/facebook/scratch.py @@ -0,0 +1,94 @@ +# This is a scratch pad for randomly selected questions + +# def char_and_rest(i, xs): +# return xs[i], xs[:i] + xs[i+1:] + +# def perms(xs): +# if len(xs) == 1: +# return [xs] +# result = [] +# for i in range(len(xs)): +# c, rest = char_and_rest(i, xs) +# for perm in perms(rest): +# result.append(c + ''.join(perm)) +# return result + +# print(perms(list("woah"))) + +# def f(take_out, dine_in, served): +# j, k = 0, 0 +# for i in range(len(served)): +# if j < len(take_out) and served[i] == take_out[j]: +# j += 1 +# elif k < len(dine_in) and served[i] == dine_in[k]: +# k += 1 +# else: +# return False +# if j < len(take_out) or k < len(dine_in): +# return False +# return True + +# take_out = [17, 8, 24] +# dine_in = [12, 19, 2] +# served = [17, 8, 12, 19, 24, 2] +# print(f(take_out, dine_in, served)) + +# def match(a, b): +# if a == '{': +# return b == '}' +# if a == '[': +# return b == ']' +# if a == '(': +# return b == ')' +# return False + +# def f(xs): +# s = [] +# for c in xs: +# if c in {'{', '[', '('}: +# s.append(c) +# elif c in {'}', ']', ')'}: +# opener = s.pop() +# if not match(opener, c): +# return False +# return len(s) == 0 + +# assert f("{[]()}") +# assert f("{[(])}") == False +# assert f("{[}") == False +# print("Success!") + +# def valid_bst(node): +# lhs = max_bst_value(node.left) if node.left else float('-inf') +# rhs = min_bst_value(node.right) if node.right else float('inf') + +# return and([ +# lhs <= node.value, +# rhs > node.value, +# valid_bst(node.left), +# valid_bst(node.right), +# ]) + +import random +import math + +def shuffle(xs): + n = len(xs) + for i in range(n - 1): + j = random.randint(i + 1, n - 1) + xs[i], xs[j] = xs[j], xs[i] + return xs + +def as_card(i): + if i not in range(1, 53): + raise Exception("Not a card") + # 1 + suit = ['Hearts', 'Clubs', 'Diamonds', 'Spades'][math.floor((i - 1) / 13)] + n = ['Ace',2,3,4,5,6,7,8,9,10,'Jack','Queen','King'][(i - 1) % 13] + return '{} of {}'.format(n, suit) + +xs = list(range(1, 53)) +print(xs) +shuffle(xs) +for x in xs: + print(as_card(x)) diff --git a/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py b/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py new file mode 100644 index 000000000000..2815dec9ee60 --- /dev/null +++ b/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py @@ -0,0 +1,22 @@ +from collections import deque +from node import Node, tree + +def find_largest(node): + while node.right: + node = node.right + return node.value + +def find_second_largest(node): + # parent of the rightmost, when rightmost is leaf + # max(rightmost.left) + prev = None + while node.right: + prev = node + node = node.right + if node.left: + return find_largest(node.left) + else: + return prev.value + +assert find_second_largest(tree) == 72 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/shuffle.py b/users/wpcarro/scratch/facebook/shuffle.py new file mode 100644 index 000000000000..21a6a96c6072 --- /dev/null +++ b/users/wpcarro/scratch/facebook/shuffle.py @@ -0,0 +1,17 @@ +from random import randint + +def get_random(i, j): + return randint(i, j) + +def shuffle(xs): + for i in range(len(xs)): + j = get_random(i, len(xs) - 1) + xs[i], xs[j] = xs[j], xs[i] + +xs = list(range(1, 53)) +print(xs) +assert len(set(xs)) == 52 +shuffle(xs) +assert len(set(xs)) == 52 +print(xs) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/stack.py b/users/wpcarro/scratch/facebook/stack.py new file mode 100644 index 000000000000..2a843e22166b --- /dev/null +++ b/users/wpcarro/scratch/facebook/stack.py @@ -0,0 +1,25 @@ +class Stack(object): + def __init__(self): + self.items = [] + + def __repr__(self): + return self.items.__repr__() + + def push(self, x): + self.items.append(x) + + def pop(self): + if not self.items: + return None + return self.items.pop() + + def peek(self): + if not self.items: + return None + return self.items[-1] + +def from_list(xs): + result = Stack() + for x in xs: + result.push(x) + return result diff --git a/users/wpcarro/scratch/facebook/stacking-boxes.py b/users/wpcarro/scratch/facebook/stacking-boxes.py new file mode 100644 index 000000000000..7a3304bc51b9 --- /dev/null +++ b/users/wpcarro/scratch/facebook/stacking-boxes.py @@ -0,0 +1,50 @@ +from random import randint + +class Box(object): + def __init__(self, w, h, d): + self.width = w + self.depth = d + self.height = h + + def __repr__(self): + return "{}x{}x{}".format(self.width, self.depth, self.height) + + def lt(self, b): + return all([ + self.width < b.width, + self.height < b.height, + self.depth < b.depth, + ]) + + def gt(self, b): + return all([ + self.width > b.width, + self.height > b.height, + self.depth > b.depth, + ]) + +def random_box(): + return Box( + randint(1, 10), + randint(1, 10), + randint(1, 10), + ) + +xs = [random_box() for _ in range(5)] + +def highest_stack(xs, cache={}): + if not xs: + return 0 + heights = [] + for i in range(len(xs)): + x, rest = xs[i], xs[0:i] + xs[i+1:] + if cache and x in cache: + height = cache[x] + else: + height = x.height + highest_stack([b for b in rest if x.gt(b)], cache) + cache[x] = height + heights += [height] + return max(heights) + +print(xs) +print(highest_stack(xs)) diff --git a/users/wpcarro/scratch/facebook/stock-price.py b/users/wpcarro/scratch/facebook/stock-price.py new file mode 100644 index 000000000000..8e42f8152311 --- /dev/null +++ b/users/wpcarro/scratch/facebook/stock-price.py @@ -0,0 +1,16 @@ +def max_profit(xs): + buy = xs[0] + profit = xs[1] - xs[0] + for price in xs[1:]: + profit = max(profit, price - buy) + buy = min(buy, price) + return profit + +xs = [([10,7,5,8,11,9], 6), + ([10,8,7,6,5], -1)] + +for x, expected in xs: + result = max_profit(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/todo.org b/users/wpcarro/scratch/facebook/todo.org new file mode 100644 index 000000000000..6ac99267dbfa --- /dev/null +++ b/users/wpcarro/scratch/facebook/todo.org @@ -0,0 +1,60 @@ +* Array and string manipulation +** DONE Merging Meeting Times +** DONE Reverse String in Place +** TODO Reverse Words +** DONE Merge Sorted Arrays +** DONE Cafe Order Checker +* Hashing and hash tables +** DONE Inflight Entertainment +** DONE Permutation Palindrome +** DONE Word Cloud Data +** DONE Top Scores +* Greedy Algorithms +** DONE Apple Stocks +** DONE Highest Product of 3 +** DONE Product of All Other Numbers +** DONE Cafe Order Checker +** DONE In-Place Shuffle +* Sorting, searching, and logarithms +** DONE Find Rotation Point +** TODO Find Repeat, Space Edition +** DONE Top Scores +** DONE Merging Meeting Times +* Trees and graphs +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +** DONE Graph Coloring +** DONE MeshMessage +** DONE Find Repeat, Space Edition BEAST MODE +* Dynamic programming and recursion +** DONE Recursive String Permutations +** DONE Compute nth Fibonacci Number +** DONE Making Change +** DONE The Cake Thief +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +* Queues and stacks +** DONE Largest Stack +** DONE Implement A Queue With Two Stacks +** DONE Parenthesis Matching +** DONE Bracket Validator +* Linked lists +** DONE Delete Node +** DONE Does This Linked List Have A Cycle? +** DONE Reverse A Linked List +** DONE Kth to Last Node in a Singly-Linked List +** DONE Find Repeat, Space Edition BEAST MODE +* General programming +** TODO Rectangular Love +** TODO Temperature Tracker +* Bit manipulation +** DONE The Stolen Breakfast Drone +* Combinatorics, probability, and other math +** TODO Which Appears Twice +** TODO Find in Ordered Set +** TODO In-Place Shuffle +** TODO Simulate 5-sided die +** TODO Simulate 7-sided die +** TODO Two Egg Problem diff --git a/users/wpcarro/scratch/facebook/top-scores.py b/users/wpcarro/scratch/facebook/top-scores.py new file mode 100644 index 000000000000..c8a10ae5f181 --- /dev/null +++ b/users/wpcarro/scratch/facebook/top-scores.py @@ -0,0 +1,20 @@ +import random +from collections import deque + +def sorted(xs): + result = [0] * 100 + for x in xs: + result[x - 1] += 1 + + answer = deque() + for i in range(len(result)): + x = result[i] + for _ in range(x): + answer.appendleft(i + 1) + + return list(answer) + +scores = [random.choice(range(70, 100)) for _ in range(20)] +print(scores) +result = sorted(scores) +print(result) diff --git a/users/wpcarro/scratch/facebook/topo-sort.py b/users/wpcarro/scratch/facebook/topo-sort.py new file mode 100644 index 000000000000..874005a01932 --- /dev/null +++ b/users/wpcarro/scratch/facebook/topo-sort.py @@ -0,0 +1,61 @@ +import random +from heapq import heappush, heappop +from collections import deque + +# A topological sort returns the vertices of a graph sorted in an ascending +# order by the number of incoming edges each vertex has. +# +# A few algorithms for solving this exist, and at the time of this writing, I +# know none. I'm going to focus on two: +# 1. Kahn's +# 2. DFS (TODO) + +def count_in_edges(graph): + result = {k: 0 for k in graph.keys()} + for xs in graph.values(): + for x in xs: + result[x] += 1 + return result + +# Kahn's algorithm for returning a topological sorting of the vertices in +# `graph`. +def kahns_sort(graph): + result = [] + q = deque() + in_edges = count_in_edges(graph) + for x in [k for k, v in in_edges.items() if v == 0]: + q.append(x) + while q: + x = q.popleft() + result.append(x) + for c in graph[x]: + in_edges[c] -= 1 + if in_edges[c] == 0: + q.append(c) + return result + +graphs = [ + { + 0: [], + 1: [], + 2: [3], + 3: [1], + 4: [0, 1], + 5: [0, 2], + }, + { + 'A': ['C', 'D'], + 'B': ['D', 'E'], + 'C': [], + 'D': ['F', 'G'], + 'E': [], + 'F': [], + 'G': ['I'], + 'H': ['I'], + 'I': [], + } +] + +print("--- Kahn's --- ") +for graph in graphs: + print(kahns_sort(graph)) diff --git a/users/wpcarro/scratch/facebook/traversals.py b/users/wpcarro/scratch/facebook/traversals.py new file mode 100644 index 000000000000..e2565a32313d --- /dev/null +++ b/users/wpcarro/scratch/facebook/traversals.py @@ -0,0 +1,100 @@ +from math import floor + +# Lists +def cycle_backwards(times, xs): + n = len(xs) + for i in range(n * times): + print(xs[n - 1 - i % n]) + +def cycle_forwards(times, xs): + n = len(xs) + for i in range(n * times): + print(xs[i % n]) + +def backwards(xs): + n = len(xs) + for i in range(n): + print(xs[n - 1 - i]) + +def forwards(xs): + for i in range(len(xs)): + print(xs[i]) + +xs = [2, 5, 6, 9, 12] + +print("Forwards") +forwards(xs) +print("Backwards") +backwards(xs) +print("Cycle forwards") +cycle_forwards(2, xs) +print("Cycle backwards") +cycle_backwards(2, xs) + +# Tables +def tblr(table): + for row in range(len(table)): + for col in range(len(table[row])): + print(table[row][col]) + +def tbrl(table): + for row in range(len(table)): + n = len(table[row]) + for col in range(n): + print(table[row][n - 1 - col]) + +def btlr(table): + n = len(table) + for row in range(n): + for col in range(len(table[row])): + print(table[n - 1 - row][col]) + +def btrl(table): + rows = len(table) + for row in range(rows): + cols = len(table[row]) + for col in range(cols): + print(table[rows - 1 - row][cols - 1 - col]) + +def special(table): + rows = len(table) + cols = len(table[0]) + for col in range(cols): + for row in range(rows): + print(table[row][col]) + +def double_bonus(table): + rows = len(table) + cols = len(table[0]) + for i in range(rows): + row = i + for col in range(cols): + print(table[row][col % cols]) + row = (row + 1) % rows + +def free(table): + rows = len(table) + cols = len(table[0]) + d = rows * cols + for i in range(d): + row = floor((i % d) / cols) + col = i % cols + print(table[row][col]) + +table = [[1,2,3,4], + [5,6,7,8]] + +print("Top->Bottom, Left->Right") +tblr(table) +print("Top->Bottom, Right->Left") +tbrl(table) +print("Bottom->Top, Left->Right") +btlr(table) +print("Bottom->Top, Right->Left") +btrl(table) +print("Special") +special(table) +print("2x Bonus") +double_bonus(table) +print("Free") +free(table) diff --git a/users/wpcarro/scratch/facebook/utils.py b/users/wpcarro/scratch/facebook/utils.py new file mode 100644 index 000000000000..9a3e8a045e88 --- /dev/null +++ b/users/wpcarro/scratch/facebook/utils.py @@ -0,0 +1,19 @@ +def init_table(rows=0, cols=0, default=None): + table = [] + for row in range(rows): + x = [] + for col in range(cols): + x.append(default) + table.append(x) + return table + +def get(table, row, col, default=0): + if row < 0 or col < 0: + return default + return table[row][col] + +def print_table(table): + result = [] + for row in range(len(table)): + result.append(' '.join([str(cell) for cell in table[row]])) + print('\n'.join(result)) diff --git a/users/wpcarro/scratch/facebook/word-cloud.py b/users/wpcarro/scratch/facebook/word-cloud.py new file mode 100644 index 000000000000..88422e3631db --- /dev/null +++ b/users/wpcarro/scratch/facebook/word-cloud.py @@ -0,0 +1,32 @@ +def normalize(x): + noise = ".,;-" + for y in noise: + if x.endswith(y): + return normalize(x[0:-1]) + if x.startswith(y): + return normalize(x[1:]) + return x.lower() + +def word_cloud(xs): + result = dict() + + for x in xs.split(' '): + k = normalize(x) + if k in result: + result[k] += 1 + else: + result[k] = 1 + + return result + +result = word_cloud("This is just the beginning. The UK will lockdown again.") +assert result.get('this') == 1 +assert result.get('is') == 1 +assert result.get('just') == 1 +assert result.get('the') == 2 +assert result.get('beginning') == 1 +assert result.get('uk') == 1 +assert result.get('will') == 1 +assert result.get('lockdown') == 1 +assert result.get('again') == 1 +print("Success!") diff --git a/users/wpcarro/scratch/groceries/.envrc b/users/wpcarro/scratch/groceries/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/groceries/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/groceries/export.hs b/users/wpcarro/scratch/groceries/export.hs new file mode 100644 index 000000000000..ed43c9a3e887 --- /dev/null +++ b/users/wpcarro/scratch/groceries/export.hs @@ -0,0 +1,22 @@ +module Main where + +import qualified Data.List as L + +(|>) :: a -> (a -> b) -> b +x |> f = f x + +-- | Ignore items with zero quantity (i.e. "0x") and comments (i.e. "#") +isUndesirableOutput :: String -> Bool +isUndesirableOutput x = + (L.isPrefixOf "- 0x" x) || (L.isPrefixOf "#" x) + +-- | Run this to export the grocery list. +main :: IO () +main = do + content <- readFile "./list.org" + content + |> lines + |> filter (not . isUndesirableOutput) + |> unlines + |> putStrLn + pure () diff --git a/users/wpcarro/scratch/groceries/list.org b/users/wpcarro/scratch/groceries/list.org new file mode 100644 index 000000000000..a823b2a8ebc3 --- /dev/null +++ b/users/wpcarro/scratch/groceries/list.org @@ -0,0 +1,112 @@ +# The sections are sorted such that the first section is likely the first area +# in the grocery store you'll encounter. +# +# This version is written for Tesco Metro in London Bridge. +* Beer +- 0x beer (6x) +* Bread +- 0x GF bread +- 0x flour +- 0x GF flour +* Produce +- 0x brocoli +- 0x green beans +- 0x green asparagus +- 2x spinach greens +- 0x romaine lettuce head +- 0x tomatoes +- 0x zucchini +- 0x lemons +- 1x limes +- 0x large carrot +- 2x garlic +- 1x green onions +- 0x onions +- 0x avocado +- 0x basil plant +- 0x jalapeno +- 0x red pepper +- 0x green pepper +- 0x cherry tomatoes +- 0x potato +- 0x bag dry black beans +- 1x Scotch Bonnet pepper +* Spices +- 0x onion powder +- 0x garlic powder +- 0x chicken bouillon +- 0x oregano +- 0x red pepper flakes +- 0x basil plant +- 0x cilantro plant +* Meat +- 0x sausages +- 0x steak +- 0x chicken breasts +- 0x chicken legs +- 0x lamb +- 0x ground beef +* Frozen +- 0x Salmon +- 0x white fish +- 0x shrimp +- 0x bag green beans +- 1x bag peas +- 0x bag corn +* Dairy +- 1x unsalted butter +- 0x coconut milk +- 2x egg cartons (12x each) +- 2x sour cream +- 0x cheddar cheese +- 0x parmesan +- 0x gouda +- 0x random cheese +* Pasta +- 0x box of quinoa +- 0x box of rice +- 1x GF pasta +- 0x tortellini / ravioli +- 0x tomato sauce +- 0x tomato paste +- 0x can diced tomatoes +- 0x pesto +* Oil +- 0x olive oil +- 0x sesame oil +- 0x avocado oil +- 0x coconut oil +- 0x white wine vinegar +* Condiments +- 0x red Tabasco +- 0x green Tabasco +- 0x habanero Tabasco +- 0x BBQ sauce +- 0x french mustard +- 0x ketchup +- 0x oyster sauce +- 0x soy sauce +- 0x Srirachi sauce +* Nuts +- 0x almonds +- 0x walnuts +- 0x peanuts +- 0x cashews +- 0x Brazil nuts +- 0x mixed nuts +- 0x peanuts +- 2x peanut butter +* Sugar +- 0x Lindt chocolate +* Asian +- 0x red curry +- 0x green curry +- 0x coconut cream +* Wine +- 0x red wine +- 0x white wine +* Miscellaneous +- 0x coffee beans +- 0x tea +- 0x AA batteries +- 0x rubbing alcohol diff --git a/users/wpcarro/scratch/groceries/shell.nix b/users/wpcarro/scratch/groceries/shell.nix new file mode 100644 index 000000000000..4d5b412a0884 --- /dev/null +++ b/users/wpcarro/scratch/groceries/shell.nix @@ -0,0 +1,5 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: []; +} diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc b/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci b/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci new file mode 100644 index 000000000000..12aab7f08e05 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci @@ -0,0 +1 @@ +:set prompt "> " diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs new file mode 100644 index 000000000000..8259606da374 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs @@ -0,0 +1,213 @@ +module ApplicativeScratch where + +import Data.Function ((&)) + +import Control.Applicative (liftA3) +import qualified Data.List as List +import qualified GHC.Base as Base + +-------------------------------------------------------------------------------- + +-- xs :: [(Integer, Integer)] +-- xs = zip [1..3] [4..6] + +-- added :: Maybe Integer +-- added = +-- (+3) <$> (lookup 3 xs) + +-------------------------------------------------------------------------------- + +-- y :: Maybe Integer +-- y = lookup 3 xs + +-- z :: Maybe Integer +-- z = lookup 2 xs + +-- tupled :: Maybe (Integer, Integer) +-- tupled = Base.liftA2 (,) y z + +-------------------------------------------------------------------------------- + +-- x :: Maybe Int +-- x = List.elemIndex 3 [1..5] + +-- y :: Maybe Int +-- y = List.elemIndex 4 [1..5] + +-- maxed :: Maybe Int +-- maxed = Base.liftA2 max x y + +-------------------------------------------------------------------------------- + +xs = [1..3] +ys = [4..6] + +x :: Maybe Integer +x = lookup 3 $ zip xs ys + +y :: Maybe Integer +y = lookup 2 $ zip xs ys + +summed :: Maybe Integer +summed = sum <$> Base.liftA2 (,) x y + +-------------------------------------------------------------------------------- + +newtype Identity a = Identity a deriving (Eq, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Applicative Identity where + pure = Identity + (Identity f) <*> (Identity x) = Identity (f x) + +-------------------------------------------------------------------------------- + +newtype Constant a b = + Constant { getConstant :: a } + deriving (Eq, Ord, Show) + +instance Functor (Constant a) where + fmap _ (Constant x) = Constant x + +instance Monoid a => Applicative (Constant a) where + pure _ = Constant mempty + (Constant x) <*> (Constant y) = Constant (x <> y) + +-------------------------------------------------------------------------------- + +one = const <$> Just "Hello" <*> Just "World" + +two :: Maybe (Integer, Integer, String, [Integer]) +two = (,,,) <$> (Just 90) + <*> (Just 10) + <*> (Just "Tierness") + <*> (Just [1..3]) + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Semigroup (List a) where + Nil <> xs = xs + xs <> Nil = xs + (Cons x xs) <> ys = Cons x (xs <> ys) + +instance Functor List where + fmap f Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Applicative List where + pure x = Cons x Nil + Nil <*> _ = Nil + _ <*> Nil = Nil + (Cons f fs) <*> xs = + (f <$> xs) <> (fs <*> xs) + +toList :: List a -> [a] +toList Nil = [] +toList (Cons x xs) = x : toList xs + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:xs) = Cons x (fromList xs) + +-------------------------------------------------------------------------------- + +newtype ZipList' a = + ZipList' [a] + deriving (Eq, Show) + +-- instance Eq a => EqProp (ZipList' a) where +-- (ZipList' lhs) =-= (ZipList' rhs) = +-- (take 1000 lhs) `eq` (take 1000 rhs) + +instance Functor ZipList' where + fmap f (ZipList' xs) = ZipList' $ fmap f xs + +instance Applicative ZipList' where + pure x = ZipList' (repeat x) + (ZipList' fs) <*> (ZipList' xs) = + ZipList' $ zipWith ($) fs xs + +-------------------------------------------------------------------------------- + +data Validation e a + = Failure e + | Success a + deriving (Eq, Show) + +instance Functor (Validation e) where + fmap f (Failure x) = Failure x + fmap f (Success x) = Success (f x) + +instance Monoid e => Applicative (Validation e) where + pure = undefined + (Success f) <*> (Success x) = Success (f x) + _ <*> (Failure x) = Failure x + (Failure x) <*> _ = Failure x + +data Error + = DivideByZero + | StackOverflow + deriving (Eq, Show) + +-------------------------------------------------------------------------------- + +stops :: String +stops = "pbtdkg" + +vowels :: String +vowels = "aeiou" + +combos :: [a] -> [b] -> [c] -> [(a, b, c)] +combos xs ys zs = + liftA3 (,,) xs ys zs + +-------------------------------------------------------------------------------- + +data Pair a = Pair a a deriving Show + +instance Functor Pair where + fmap f (Pair x y) = Pair (f x) (f y) + +instance Applicative Pair where + pure x = Pair x x + (Pair f g) <*> (Pair x y) = Pair (f x) (g x) + +p :: Pair Integer +p = Pair 1 2 + +-------------------------------------------------------------------------------- + +data Two a b = Two a b + +instance Functor (Two a) where + fmap f (Two x y) = Two x (f y) + +instance Monoid a => Applicative (Two a) where + pure x = Two mempty x + _ <*> _ = undefined + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c + +instance Functor (Three a b) where + fmap f (Three x y z) = Three x y (f z) + +instance (Monoid a, Monoid b) => Applicative (Three a b) where + pure x = Three mempty mempty x + (Three a b f) <*> (Three x y z) = Three (a <> x) (b <> y) (f z) + +-------------------------------------------------------------------------------- + +data Three' a b = Three' a b b + +instance Functor (Three' a) where + fmap f (Three' x y z) = Three' x (f y) (f z) + +instance Monoid a => Applicative (Three' a) where + pure x = Three' mempty x x + (Three' a f g) <*> (Three' x y z) = Three' (a <> x) (f y) (g z) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs new file mode 100644 index 000000000000..bb1f89987e29 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs @@ -0,0 +1,60 @@ +module BasicLibrariesScratch where + +import Data.Function ((&)) + +-------------------------------------------------------------------------------- +newtype DList a = DL { unDL :: [a] -> [a] } + +instance (Show a) => Show (DList a) where + show (DL x) = "DL " ++ show (x []) + +-- | Create an empty difference list. +emptyDList :: DList a +emptyDList = DL $ \xs -> xs +{-# INLINE emptyDList #-} + +-- | Create a difference list with `x` as the only member. +singleton :: a -> DList a +singleton x = DL $ \xs -> x : xs +{-# INLINE singleton #-} + +-- | Convert the DList into a list. +toList :: DList a -> [a] +toList (DL unDL) = unDL mempty +{-# INLINE toList #-} + +-- | Add an element to the end of a DList. +infixr `snoc` +snoc :: a -> DList a -> DList a +snoc x (DL xs) = DL $ \ys -> xs (x : ys) +{-# INLINE snoc #-} + +-- | Add an element to the beginning of a DList. +infixr `cons` +cons :: a -> DList a -> DList a +cons x (DL xs) = DL $ \ys -> x : xs ys +{-# INLINE cons #-} + +-- | Combine two DLists together. +append :: DList a -> DList a -> DList a +append (DL xs) (DL ys) = DL $ \zs -> zs & ys & xs +{-# INLINE append #-} + +-------------------------------------------------------------------------------- +data Queue a = + Queue { one :: [a] + , two :: [a] + } deriving (Show, Eq) + +emptyQueue :: Queue a +emptyQueue = Queue mempty mempty + +enqueue :: a -> Queue a -> Queue a +enqueue x (Queue en de) = Queue (x:en) de + +dequeue :: Queue a -> Maybe (a, Queue a) +dequeue (Queue [] []) = Nothing +dequeue (Queue en []) = + let (d:de) = reverse en + in Just (d, Queue de []) +dequeue (Queue en (d:de)) = Just (d, Queue en de) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs new file mode 100644 index 000000000000..378cfb7ceae6 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs @@ -0,0 +1,75 @@ +module ComposingTypesScratch where + +import Data.Function ((&)) +import Data.Bifunctor + +import qualified Data.Foldable as F + +-------------------------------------------------------------------------------- + +newtype Identity a = + Identity { getIdentity :: a } + deriving (Eq, Show) + +newtype Compose f g a = + Compose { getCompose :: f (g a) } + deriving (Eq, Show) + +-------------------------------------------------------------------------------- + +instance (Functor f, Functor g) => Functor (Compose f g) where + fmap f (Compose getCompose) = Compose $ (fmap . fmap) f getCompose + +instance (Applicative f, Applicative g) => Applicative (Compose f g) where + pure x = x & pure & pure & Compose + fgf <*> fga = undefined + +-------------------------------------------------------------------------------- + +instance (Foldable f, Foldable g) => Foldable (Compose f g) where + foldMap toMonoid x = undefined + +instance (Traversable f, Traversable g) => Traversable (Compose f g) where + traverse = undefined + +-------------------------------------------------------------------------------- + +data Deux a b = Deux a b deriving (Show, Eq) + +instance Bifunctor Deux where + bimap f g (Deux x y) = Deux (f x) (g y) + +data Const a b = Const a deriving (Show, Eq) + +instance Bifunctor Const where + bimap f _ (Const x) = Const (f x) + +data Drei a b c = Drei a b c deriving (Show, Eq) + +instance Bifunctor (Drei a) where + bimap f g (Drei x y z) = Drei x (f y) (g z) + +data SuperDrei a b c = SuperDrei a b deriving (Show, Eq) + +instance Bifunctor (SuperDrei a) where + bimap f g (SuperDrei x y) = SuperDrei x (f y) + +data SemiDrei a b c = SemiDrei a deriving (Show, Eq) + +instance Bifunctor (SemiDrei a) where + bimap _ _ (SemiDrei x) = SemiDrei x + +data Quadriceps a b c d = Quadzzz a b c d + +instance Bifunctor (Quadriceps a b) where + bimap f g (Quadzzz w x y z) = Quadzzz w x (f y) (g z) + +-- | Analogue for Either +data LeftRight a b + = Failure a + | Success b + deriving (Show, Eq) + +instance Bifunctor LeftRight where + bimap f _ (Failure x) = Failure (f x) + bimap _ g (Success y) = Success (g y) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs new file mode 100644 index 000000000000..5b59d9e9ba50 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs @@ -0,0 +1,107 @@ +module FoldableScratch where + +import Data.Function ((&)) + +-------------------------------------------------------------------------------- + +sum :: (Foldable t, Num a) => t a -> a +sum xs = + foldr (+) 0 xs + +product :: (Foldable t, Num a) => t a -> a +product xs = + foldr (*) 1 xs + +elem :: (Foldable t, Eq a) => a -> t a -> Bool +elem y xs = + foldr (\x acc -> if acc then acc else y == x) False xs + +minimum :: (Foldable t, Ord a) => t a -> Maybe a +minimum xs = + foldr (\x acc -> + case acc of + Nothing -> Just x + Just curr -> Just (min curr x)) Nothing xs + +maximum :: (Foldable t, Ord a) => t a -> Maybe a +maximum xs = + foldr (\x acc -> + case acc of + Nothing -> Nothing + Just curr -> Just (max curr x)) Nothing xs + +-- TODO: How could I use QuickCheck to see if Prelude.null and this null return +-- the same results for the same inputs? +null :: (Foldable t) => t a -> Bool +null xs = + foldr (\_ _ -> False) True xs + +length :: (Foldable t) => t a -> Int +length xs = + foldr (\_ acc -> acc + 1) 0 xs + +toList :: (Foldable t) => t a -> [a] +toList xs = + reverse $ foldr (\x acc -> x : acc) [] xs + +fold :: (Foldable t, Monoid m) => t m -> m +fold xs = + foldr mappend mempty xs + +foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m +foldMap f xs = + foldr (\x acc -> mappend (f x) acc) mempty xs + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Foldable List where + foldr f acc (Cons x rest) = foldr f (f x acc) rest + foldr f acc Nil = acc + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:rest) = Cons x (fromList rest) + +-------------------------------------------------------------------------------- + +data Constant a b = Constant b deriving (Eq, Show) + +-- TODO: Is this correct? +instance Foldable (Constant a) where + foldr f acc (Constant x) = f x acc + +-------------------------------------------------------------------------------- + +data Two a b = Two a b deriving (Eq, Show) + +instance Foldable (Two a) where + foldr f acc (Two x y) = f y acc + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c deriving (Eq, Show) + +instance Foldable (Three a b) where + foldr f acc (Three x y z) = f z acc + +-------------------------------------------------------------------------------- + +data Three' a b = Three' a b b deriving (Eq, Show) + +instance Foldable (Three' a) where + foldr f acc (Three' x y z) = acc & f z & f y + +-------------------------------------------------------------------------------- + +data Four' a b = Four' a b b b deriving (Eq, Show) + +instance Foldable (Four' a) where + foldr f acc (Four' w x y z) = acc & f z & f y & f x + +-------------------------------------------------------------------------------- + +filterF :: (Applicative f, Foldable t, Monoid (f a)) => (a -> Bool) -> t a -> f a +filterF pred xs = + foldr (\x acc -> if pred x then pure x `mappend` acc else acc) mempty xs diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs new file mode 100644 index 000000000000..1de8937fced4 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs @@ -0,0 +1,35 @@ +module IOScratch where + +import qualified System.Environment as SE +import qualified System.IO as SIO +-------------------------------------------------------------------------------- + +docs :: String +docs = "Pass -e to encrypt and -d to decrypt." + +encryptStdin :: IO () +encryptStdin = do + char <- SIO.hGetChar SIO.stdin + -- encrypt char + SIO.hPutStr SIO.stdout [char] + +decryptStdin :: IO () +decryptStdin = do + char <- SIO.hGetChar SIO.stdin + -- decrypt char + SIO.hPutStr SIO.stdout [char] + +main :: IO () +main = do + args <- SE.getArgs + case args of + [] -> + putStrLn $ "You did not pass enough arguments. " ++ docs + ["-e"] -> + encryptStdin + ["-d"] -> + decryptStdin + [x] -> + putStrLn $ "You passed an unsupported option: " ++ x ++ ". " ++ docs + _ -> + putStrLn $ "You passed too many arguments. " ++ docs diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs new file mode 100644 index 000000000000..3a780fc16c82 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs @@ -0,0 +1,183 @@ +module MonadTransformersScratch where + +import Control.Monad +import qualified Control.Monad.Trans.Maybe as M +import qualified Control.Monad.Trans.Reader as R +import qualified Control.Monad.Trans.State as S +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +newtype MaybeT m a = + MaybeT { runMaybeT :: m (Maybe a) } + +instance (Functor f) => Functor (MaybeT f) where + fmap f (MaybeT run) = + MaybeT $ (fmap . fmap) f run + +instance (Applicative m) => Applicative (MaybeT m) where + pure x = x & pure & pure & MaybeT + _ <*> _ = undefined + +instance (Monad m) => Monad (MaybeT m) where + return = pure + (MaybeT ma) >>= f = MaybeT $ do + maybeX <- ma + case maybeX of + Nothing -> pure Nothing + Just x -> x & f & runMaybeT + +-------------------------------------------------------------------------------- + +newtype EitherT e m a = + EitherT { runEitherT :: m (Either e a) } + +instance (Functor m) => Functor (EitherT e m) where + fmap f (EitherT mEither) = + EitherT $ (fmap . fmap) f mEither + +instance (Applicative m) => Applicative (EitherT e m) where + pure x = EitherT $ (pure . pure) x + EitherT mEitherF <*> EitherT mEitherX = + EitherT $ (fmap (<*>) mEitherF) <*> mEitherX + +instance (Monad m) => Monad (EitherT e m) where + return = pure + EitherT mEitherX >>= f = EitherT $ do + eitherX <- mEitherX + case eitherX of + Left x -> pure $ Left x + Right x -> runEitherT $ f x + +swapEither :: Either l r -> Either r l +swapEither (Left x) = Right x +swapEither (Right x) = Left x + +swapEitherT :: (Functor m) => EitherT e m a -> EitherT a m e +swapEitherT (EitherT mEitherX) = + EitherT $ fmap swapEither mEitherX + +eitherT :: Monad m => (a -> m c) -> (b -> m c) -> EitherT a m b -> m c +eitherT aToMC bToMC (EitherT mEitherX) = do + eitherX <- mEitherX + case eitherX of + Left x -> aToMC x + Right x -> bToMC x + +-------------------------------------------------------------------------------- + +newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a } + +instance (Functor m) => Functor (ReaderT r m) where + fmap f (ReaderT rma) = + ReaderT $ (fmap . fmap) f rma + +instance (Applicative m) => Applicative (ReaderT r m) where + pure x = x & pure & pure & ReaderT + ReaderT f <*> ReaderT x = ReaderT $ fmap (<*>) f <*> x + +-- instance (Monad m) => Monad (ReaderT r m) where +-- return = pure +-- ReaderT rma >>= f = +-- ReaderT $ \r -> do +-- a <- rma r +-- runReaderT (f a) r +-- -------------------------------------------------------------------------------- + +rDec :: Num a => R.Reader a a +rDec = R.ReaderT $ \x -> pure $ x + 1 + +rShow :: Show a => R.Reader a String +rShow = R.ReaderT $ \x -> pure $ show x + +rPrintAndInc :: (Num a, Show a) => R.ReaderT a IO a +rPrintAndInc = R.ReaderT $ \x -> + putStrLn ("Hi: " ++ show x) >> pure (x + 1) + +sPrintIncAccum :: (Num a, Show a) => S.StateT a IO String +sPrintIncAccum = S.StateT $ \x -> do + putStrLn ("Hi: " ++ show x) + pure (show x, x + 1) + +-------------------------------------------------------------------------------- + +isValid :: String -> Bool +isValid v = '!' `elem` v + +maybeExcite :: M.MaybeT IO String +maybeExcite = M.MaybeT $ do + x <- getLine + putStrLn "" + case isValid x of + False -> pure Nothing + True -> pure $ Just x + +doExcite :: IO () +doExcite = do + putStr "Say something *exciting*: " + excite <- M.runMaybeT maybeExcite + case excite of + Nothing -> putStrLn "Gonna need some more excitement..." + Just x -> putStrLn "Now THAT'S exciting...nice!" + +-------------------------------------------------------------------------------- + +data Participant + = Man + | Machine + deriving (Show, Eq) + +newtype Hand = Hand (Integer, Integer) deriving (Show, Eq) + +newtype Score = Score (Integer, Integer) deriving (Show, Eq) + +getLineLn :: String -> IO String +getLineLn prompt = do + putStr prompt + x <- getLine + putStrLn "" + pure x + +promptGuess :: IO Hand +promptGuess = do + fingers <- getLineLn "How many fingers (0-5): " + guess <- getLineLn "Guess: " + pure $ Hand (read guess, read fingers) + +aiGuess :: IO Hand +aiGuess = pure $ Hand (2, 3) + +whoWon :: Hand -> Hand -> Maybe Participant +whoWon (Hand (guessA, fingersA)) (Hand (guessB, fingersB)) + | guessA == guessB && guessA == (fingersA + fingersB) = Nothing + | guessA == (fingersA + fingersB) = Just Man + | guessB == (fingersA + fingersB) = Just Machine + | otherwise = Nothing + +initScore :: Score +initScore = Score (0, 0) + +printScore :: Score -> IO () +printScore (Score (man, machine)) = + putStrLn $ "Man: " ++ show man ++ " Machine: " ++ show machine + +startMorra :: S.StateT Score IO () +startMorra = S.StateT $ \(Score (man, machine)) -> do + Hand (guessA, fingersA) <- promptGuess + Hand (guessB, fingersB) <- aiGuess + putStrLn $ "P: " ++ show fingersA ++ "," ++ show guessA + putStrLn $ "C: " ++ show fingersB ++ "," ++ show guessB + case whoWon (Hand (guessA, fingersA)) (Hand (guessB, fingersB)) of + Nothing -> do + putStrLn "Nobody won..." + printScore (Score (man, machine)) + pure ((), Score (man, machine)) + Just Man -> do + putStrLn "Man won!" + printScore (Score (man + 1, machine)) + pure ((), Score (man + 1, machine)) + Just Machine -> do + putStrLn "Oh no... Machine won..." + printScore (Score (man, machine + 1)) + pure ((), Score (man, machine + 1)) + +playMorra = S.runStateT (forever startMorra) initScore diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs new file mode 100644 index 000000000000..2f80b457b125 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs @@ -0,0 +1,178 @@ +module MonadScratch where + +import Data.Function ((&)) +import Test.QuickCheck +import Test.QuickCheck.Checkers +import Control.Applicative (liftA2) +import qualified Control.Monad as Monad + +-------------------------------------------------------------------------------- + +bind :: Monad m => (a -> m b) -> m a -> m b +bind f x = Monad.join $ fmap f x + +-------------------------------------------------------------------------------- + +fTrigger :: Functor f => f (Int, String, [Int]) +fTrigger = undefined + +aTrigger :: Applicative a => a (Int, String, [Int]) +aTrigger = undefined + +mTrigger :: Monad m => m (Int, String, [Int]) +mTrigger = undefined + +-------------------------------------------------------------------------------- + +data Sum a b + = Fst a + | Snd b + deriving (Eq, Show) + +instance (Eq a, Eq b) => EqProp (Sum a b) where + (=-=) = eq + +instance (Arbitrary a, Arbitrary b) => Arbitrary (Sum a b) where + arbitrary = frequency [ (1, Fst <$> arbitrary) + , (1, Snd <$> arbitrary) + ] + +instance Functor (Sum a) where + fmap f (Fst x) = Fst x + fmap f (Snd x) = Snd (f x) + +instance Applicative (Sum a) where + pure x = Snd x + (Snd f) <*> (Snd x) = Snd (f x) + (Snd f) <*> (Fst x) = Fst x + (Fst x) <*> _ = Fst x + +instance Monad (Sum a) where + (Fst x) >>= _ = Fst x + (Snd x) >>= f = f x + +-------------------------------------------------------------------------------- + +data Nope a = NopeDotJpg deriving (Eq, Show) + +instance Arbitrary (Nope a) where + arbitrary = pure NopeDotJpg + +instance EqProp (Nope a) where + (=-=) = eq + +instance Functor Nope where + fmap f _ = NopeDotJpg + +instance Applicative Nope where + pure _ = NopeDotJpg + _ <*> _ = NopeDotJpg + +instance Monad Nope where + NopeDotJpg >>= f = NopeDotJpg + +-------------------------------------------------------------------------------- + +data BahEither b a + = PLeft a + | PRight b + deriving (Eq, Show) + +instance (Arbitrary b, Arbitrary a) => Arbitrary (BahEither b a) where + arbitrary = frequency [ (1, PLeft <$> arbitrary) + , (1, PRight <$> arbitrary) + ] + +instance (Eq a, Eq b) => EqProp (BahEither a b) where + (=-=) = eq + +instance Functor (BahEither b) where + fmap f (PLeft x) = PLeft (f x) + fmap _ (PRight x) = PRight x + +instance Applicative (BahEither b) where + pure = PLeft + (PRight x) <*> _ = PRight x + (PLeft f) <*> (PLeft x) = PLeft (f x) + _ <*> (PRight x) = PRight x + +instance Monad (BahEither b) where + (PRight x) >>= _ = PRight x + (PLeft x) >>= f = f x + +-------------------------------------------------------------------------------- + +newtype Identity a = Identity a + deriving (Eq, Ord, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Applicative Identity where + pure = Identity + (Identity f) <*> (Identity x) = Identity (f x) + +instance Monad Identity where + (Identity x) >>= f = f x + +-------------------------------------------------------------------------------- + +data List a + = Nil + | Cons a (List a) + deriving (Eq, Show) + +instance Arbitrary a => Arbitrary (List a) where + arbitrary = frequency [ (1, pure Nil) + , (1, Cons <$> arbitrary <*> arbitrary) + ] + +instance Eq a => EqProp (List a) where + (=-=) = eq + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:xs) = Cons x (fromList xs) + +instance Semigroup (List a) where + Nil <> xs = xs + xs <> Nil = xs + (Cons x xs) <> ys = + Cons x (xs <> ys) + +instance Functor List where + fmap f Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Applicative List where + pure x = Cons x Nil + Nil <*> _ = Nil + _ <*> Nil = Nil + (Cons f fs) <*> xs = + (f <$> xs) <> (fs <*> xs) + +instance Monad List where + Nil >>= _ = Nil + (Cons x xs) >>= f = (f x) <> (xs >>= f) + +-------------------------------------------------------------------------------- + +j :: Monad m => m (m a) -> m a +j = Monad.join + +l1 :: Monad m => (a -> b) -> m a -> m b +l1 = Monad.liftM + +l2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c +l2 = Monad.liftM2 + +a :: Monad m => m a -> m (a -> b) -> m b +a = flip (<*>) + +meh :: Monad m => [a] -> (a -> m b) -> m [b] +meh xs f = flipType $ f <$> xs + +flipType :: Monad m => [m a] -> m [a] +flipType [] = pure mempty +flipType (m:ms) = + m >>= (\x -> (x:) <$> flipType ms) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs new file mode 100644 index 000000000000..42608fb0c961 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs @@ -0,0 +1,6 @@ +module NonStrictnessScratch where + +x = undefined +y = "blah" +main = do + print $ snd (x, x `seq` y) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs new file mode 100644 index 000000000000..7cb7b4a1bbc1 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs @@ -0,0 +1,149 @@ +module Reader where + +import Data.Char +import Data.Function ((&)) +import Data.Functor ((<&>)) +import qualified Control.Applicative as A +import qualified Data.Maybe as MB + +cap :: String -> String +cap xs = xs <&> toUpper + +rev :: String -> String +rev = reverse + +compose :: String -> String +compose xs = xs & rev . cap + +fmapped :: String -> String +fmapped xs = xs & rev <$> cap + +tupled :: String -> (String, String) +tupled xs = A.liftA2 (,) cap rev $ xs + +tupled' :: String -> (String, String) +tupled' = do + capResult <- cap + revResult <- rev + pure (revResult, capResult) + +-------------------------------------------------------------------------------- + +newtype Reader r a = Reader { runReader :: r -> a } + +ask :: Reader a a +ask = Reader id + +-------------------------------------------------------------------------------- + +newtype HumanName = HumanName String + deriving (Eq, Show) + +newtype DogName = DogName String + deriving (Eq, Show) + +newtype Address = Address String + deriving (Eq, Show) + +data Person + = Person + { humanName :: HumanName + , dogName :: DogName + , address :: Address + } deriving (Eq, Show) + +data Dog + = Dog + { dogsName :: DogName + , dogsAddress :: Address + } deriving (Eq, Show) + +pers :: Person +pers = + Person (HumanName "Big Bird") + (DogName "Barkley") + (Address "Sesame Street") + +chris :: Person +chris = + Person (HumanName "Chris Allen") + (DogName "Papu") + (Address "Austin") + +getDog :: Person -> Dog +getDog p = + Dog (dogName p) (address p) + +getDogR :: Person -> Dog +getDogR = + A.liftA2 Dog dogName address + +-------------------------------------------------------------------------------- + +myLiftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c +myLiftA2 f x y = + f <$> x <*> y + +asks :: (r -> a) -> Reader r a +asks f = Reader f + +-------------------------------------------------------------------------------- + +instance Functor (Reader a) where + fmap f (Reader ab) = Reader $ f . ab + +instance Applicative (Reader a) where + pure x = Reader $ \_ -> x + (Reader rab) <*> (Reader ra) = Reader $ do + ab <- rab + fmap ab ra + +-------------------------------------------------------------------------------- + +instance Monad (Reader r) where + return = pure + -- (>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b + (Reader x) >>= f = undefined + +-------------------------------------------------------------------------------- + +x = [1..3] +y = [4..6] +z = [7..9] + +xs :: Maybe Integer +xs = zip x y & lookup 3 + +ys :: Maybe Integer +ys = zip y z & lookup 6 + +zs :: Maybe Integer +zs = zip x y & lookup 4 + +z' :: Integer -> Maybe Integer +z' n = zip x y & lookup n + +x1 :: Maybe (Integer, Integer) +x1 = A.liftA2 (,) xs ys + +x2 :: Maybe (Integer, Integer) +x2 = A.liftA2 (,) ys zs + +x3 :: Integer -> (Maybe Integer, Maybe Integer) +x3 n = (z' n, z' n) + +summed :: Num a => (a, a) -> a +summed (x, y) = x + y + +bolt :: Integer -> Bool +bolt x = x > 3 && x < 8 + +main :: IO () +main = do + print $ sequenceA [Just 3, Just 2, Just 1] + print $ sequenceA [x, y] + print $ sequenceA [xs, ys] + print $ summed <$> ((,) <$> xs <*> ys) + print $ bolt 7 + print $ bolt <$> z + print $ sequenceA [(>3), (<8) ,even] 7 diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix b/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix new file mode 100644 index 000000000000..b594a4207e48 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + quickcheck-simple + checkers + ]; +} diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs new file mode 100644 index 000000000000..f63e0ecdf11c --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs @@ -0,0 +1,93 @@ +module StateScratch where + +-------------------------------------------------------------------------------- +import System.Random +-- import Control.Monad.Trans.State +import Data.Function ((&)) + +import qualified Control.Applicative as Ap +import qualified Control.Monad as M +-------------------------------------------------------------------------------- + +data Die + = DieOne + | DieTwo + | DieThree + | DieFour + | DieFive + | DieSix + deriving (Eq, Show) + +intToDie :: Integer -> Maybe Die +intToDie 1 = Just DieOne +intToDie 2 = Just DieTwo +intToDie 3 = Just DieThree +intToDie 4 = Just DieFour +intToDie 5 = Just DieFive +intToDie 6 = Just DieSix +intToDie _ = Nothing + +rollDie :: Moi StdGen Die +rollDie = do + (n, s) <- randomR (1, 6) + case intToDie n of + Just d -> pure (d, s) + Nothing -> pure (DieOne, s) + +rollsToGetN :: Integer -> StdGen -> [Die] +rollsToGetN n g = go 0 [] g + where + go sum result gen + | sum >= n = result + | otherwise = + let (dice, nextGen) = randomR (1, 6) gen + in case intToDie dice of + Nothing -> go (sum + dice) result nextGen + Just d -> go (sum + dice) (d : result) nextGen + +-------------------------------------------------------------------------------- + +newtype Moi s a = Moi { runMoi :: s -> (a, s) } + +instance Functor (Moi s) where + fmap f (Moi run) = + Moi $ \s -> let (x, t) = run s + in (f x, t) + +instance Applicative (Moi s) where + pure x = Moi $ \s -> (x, s) + (Moi f) <*> (Moi run) = + Moi $ \s -> let (g, t) = f s + (x, u) = run t + in (g x, u) + +instance Monad (Moi s) where + (Moi run1) >>= f = + Moi $ \s -> let (x, t) = run1 s + (Moi run2) = f x + in run2 t + +-------------------------------------------------------------------------------- + +fizzBuzz :: Integer -> String +fizzBuzz n | n `mod` 15 == 0 = "FizzBuzz" + | n `mod` 5 == 0 = "Buzz" + | n `mod` 3 == 0 = "Fizz" + | otherwise = show n + +-------------------------------------------------------------------------------- + +get :: Moi s s +get = Moi $ \s -> (s, s) + +put :: s -> Moi s () +put x = Moi $ \s -> ((), x) + +exec :: Moi s a -> s -> s +exec (Moi run) x = x & run & snd + +eval :: Moi s a -> s -> a +eval (Moi run) x = x & run & fst + +modify :: (s -> s) -> Moi s () +modify f = Moi $ \s -> ((), f s) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs new file mode 100644 index 000000000000..5dc4ea411bc2 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs @@ -0,0 +1,131 @@ +module TraversableScratch where + +import qualified Data.Foldable as F + +import Test.QuickCheck + +newtype Identity a = Identity a + deriving (Eq, Ord, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Foldable Identity where + foldMap f (Identity x) = f x + +instance Traversable Identity where + traverse f (Identity x) = Identity <$> f x + +-------------------------------------------------------------------------------- + +data Optional a + = Nada + | Some a + deriving (Eq, Show) + +instance Functor Optional where + fmap f Nada = Nada + fmap f (Some x) = Some (f x) + +instance Foldable Optional where + foldMap f Nada = mempty + foldMap f (Some x) = f x + +instance Traversable Optional where + traverse f Nada = pure Nada + traverse f (Some x) = Some <$> f x + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Functor List where + fmap _ Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Foldable List where + foldMap f Nil = mempty + foldMap f (Cons x xs) = mappend (f x) (foldMap f xs) + +instance Traversable List where + sequenceA Nil = pure Nil + sequenceA (Cons x xs) = Cons <$> x <*> sequenceA xs + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c + deriving (Eq, Show) + +instance Functor (Three a b) where + fmap f (Three x y z) = Three x y (f z) + +instance Foldable (Three a b) where + foldMap f (Three _ _ z) = f z + +instance Traversable (Three a b) where + sequenceA (Three x y z) = (\z' -> Three x y z') <$> z + +-------------------------------------------------------------------------------- + +data Pair a b = Pair a b + deriving (Eq, Show) + +instance Functor (Pair a) where + fmap f (Pair x y) = Pair x (f y) + +instance Foldable (Pair a) where + foldMap f (Pair x y) = f y + +instance Traversable (Pair a) where + sequenceA (Pair x y) = (\y' -> Pair x y') <$> y + +-------------------------------------------------------------------------------- + +data Big a b = Big a b b + deriving (Eq, Show) + +instance Functor (Big a) where + fmap f (Big x y z) = Big x (f y) (f z) + +instance Foldable (Big a) where + foldMap f (Big x y z) = f y <> f z + +instance Traversable (Big a) where + sequenceA (Big x y z) = (\y' z' -> Big x y' z') <$> y <*> z + +-------------------------------------------------------------------------------- + +data Bigger a b = Bigger a b b b + deriving (Eq, Show) + +instance Functor (Bigger a) where + fmap f (Bigger w x y z) = Bigger w (f x) (f y) (f z) + +instance Foldable (Bigger a) where + foldMap f (Bigger w x y z) = f x <> f y <> f z + +instance Traversable (Bigger a) where + sequenceA (Bigger w x y z) = (\x' y' z' -> Bigger w x' y' z') <$> x <*> y <*> z + +-------------------------------------------------------------------------------- + +data Tree a + = Empty + | Leaf a + | Node (Tree a) a (Tree a) + deriving (Eq, Show) + +instance Functor Tree where + fmap f Empty = Empty + fmap f (Leaf x) = Leaf (f x) + fmap f (Node lhs x rhs) = Node (fmap f lhs) (f x) (fmap f rhs) + +instance Foldable Tree where + foldMap f Empty = mempty + foldMap f (Leaf x) = f x + foldMap f (Node lhs x rhs) = (foldMap f lhs) <> (f x) <> (foldMap f rhs) + +instance Traversable Tree where + sequenceA Empty = pure Empty + sequenceA (Leaf x) = Leaf <$> x + sequenceA (Node lhs x rhs) = Node <$> sequenceA lhs <*> x <*> sequenceA rhs diff --git a/users/wpcarro/secrets.json.secret b/users/wpcarro/secrets.json.secret new file mode 100644 index 000000000000..d4c02bf69365 --- /dev/null +++ b/users/wpcarro/secrets.json.secret Binary files differdiff --git a/users/wpcarro/third_party/README.md b/users/wpcarro/third_party/README.md new file mode 100644 index 000000000000..29e498a37726 --- /dev/null +++ b/users/wpcarro/third_party/README.md @@ -0,0 +1,5 @@ +# third_party + +The `third_party` directory hosts Nix expressions that package software that I +cannot or have not found in other Nix package repositorys like `nixpkgs` or +`depot`. diff --git a/users/wpcarro/third_party/default.nix b/users/wpcarro/third_party/default.nix new file mode 100644 index 000000000000..949110373fb2 --- /dev/null +++ b/users/wpcarro/third_party/default.nix @@ -0,0 +1,5 @@ +{ pkgs, briefcase, depot, ... }: + +# Exposing these to be available as briefcase.third_party.pkgs for example. + +{ inherit pkgs briefcase depot; } diff --git a/users/wpcarro/third_party/lisp/anaphora.nix b/users/wpcarro/third_party/lisp/anaphora.nix new file mode 100644 index 000000000000..bf4bf663f4d4 --- /dev/null +++ b/users/wpcarro/third_party/lisp/anaphora.nix @@ -0,0 +1,17 @@ +{ depot, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tokenrove/anaphora.git"; + rev = "aeace4c68cf55098a67112750b28f8f2dc6d0e30"; + }; +in depot.nix.buildLisp.library { + name = "anaphora"; + deps = []; + srcs = [ + "${src}/packages.lisp" + "${src}/early.lisp" + "${src}/symbolic.lisp" + "${src}/anaphora.lisp" + ]; +} diff --git a/users/wpcarro/third_party/lisp/cl-arrows.nix b/users/wpcarro/third_party/lisp/cl-arrows.nix new file mode 100644 index 000000000000..4c09d688fae7 --- /dev/null +++ b/users/wpcarro/third_party/lisp/cl-arrows.nix @@ -0,0 +1,15 @@ +{ depot, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/nightfly19/cl-arrows.git"; + rev = "cbb46b69a7de40f1161c9caaf6cef93b3af9994f"; + }; +in depot.nix.buildLisp.library { + name = "cl-arrows"; + deps = []; + srcs = [ + "${src}/packages.lisp" + "${src}/arrows.lisp" + ]; +} diff --git a/users/wpcarro/third_party/lisp/cl-colors.nix b/users/wpcarro/third_party/lisp/cl-colors.nix new file mode 100644 index 000000000000..fa35b755dbbc --- /dev/null +++ b/users/wpcarro/third_party/lisp/cl-colors.nix @@ -0,0 +1,20 @@ +{ depot, briefcase, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/cl-colors.git"; + rev = "827410584553f5c717eec6182343b7605f707f75"; + }; +in depot.nix.buildLisp.library { + name = "cl-colors"; + deps = [ + depot.third_party.lisp.alexandria + briefcase.third_party.lisp.let-plus + ]; + srcs = [ + "${src}/package.lisp" + "${src}/colors.lisp" + "${src}/colornames.lisp" + "${src}/hexcolors.lisp" + ]; +} diff --git a/users/wpcarro/third_party/lisp/let-plus.nix b/users/wpcarro/third_party/lisp/let-plus.nix new file mode 100644 index 000000000000..a750443fd1c8 --- /dev/null +++ b/users/wpcarro/third_party/lisp/let-plus.nix @@ -0,0 +1,19 @@ +{ depot, briefcase, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/let-plus.git"; + rev = "7cf18b29ed0fe9c667a9a6a101b08ab9661a59e9"; + }; +in depot.nix.buildLisp.library { + name = "let-plus"; + deps = [ + depot.third_party.lisp.alexandria + briefcase.third_party.lisp.anaphora + ]; + srcs = [ + "${src}/package.lisp" + "${src}/let-plus.lisp" + "${src}/extensions.lisp" + ]; +} diff --git a/users/wpcarro/third_party/lisp/linear-programming.nix b/users/wpcarro/third_party/lisp/linear-programming.nix new file mode 100644 index 000000000000..fc95787a1d85 --- /dev/null +++ b/users/wpcarro/third_party/lisp/linear-programming.nix @@ -0,0 +1,26 @@ +{ depot, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/neil-lindquist/linear-programming.git"; + rev = "8c8d55e7584773b90c4ba4b225c5f2008f4c474a"; + }; +in depot.nix.buildLisp.library { + name = "linear-programming"; + deps = [ + (depot.nix.buildLisp.bundled "uiop") + depot.third_party.lisp.iterate + depot.third_party.lisp.alexandria + ]; + srcs = [ + "${src}/src/conditions.lisp" + "${src}/src/expressions.lisp" + "${src}/src/simplex.lisp" + "${src}/src/system-info.lisp" + "${src}/src/utils.lisp" + "${src}/src/problem.lisp" + "${src}/src/solver.lisp" + "${src}/src/external-formats.lisp" + "${src}/src/all.lisp" + ]; +} diff --git a/users/wpcarro/third_party/lisp/prove.nix b/users/wpcarro/third_party/lisp/prove.nix new file mode 100644 index 000000000000..7c5879b1fded --- /dev/null +++ b/users/wpcarro/third_party/lisp/prove.nix @@ -0,0 +1,31 @@ +{ depot, briefcase, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/fukamachi/prove.git"; + rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; + }; +in depot.nix.buildLisp.library { + name = "prove"; + deps = [ + depot.third_party.lisp.cl-ppcre + depot.third_party.lisp.cl-ansi-text + depot.third_party.lisp.alexandria + depot.third_party.lisp.uiop + briefcase.third_party.lisp.cl-colors + ]; + srcs = [ + "${src}/src/asdf.lisp" + "${src}/src/suite.lisp" + "${src}/src/color.lisp" + "${src}/src/output.lisp" + "${src}/src/prove.lisp" + "${src}/src/report.lisp" + "${src}/src/reporter.lisp" + "${src}/src/test.lisp" + "${src}/src/reporter/dot.lisp" + "${src}/src/reporter/fiveam.lisp" + "${src}/src/reporter/list.lisp" + "${src}/src/reporter/tap.lisp" + ]; +} diff --git a/users/wpcarro/todo-lists/imdb/db.sqlite3 b/users/wpcarro/todo-lists/imdb/db.sqlite3 new file mode 100644 index 000000000000..bb893387ecd3 --- /dev/null +++ b/users/wpcarro/todo-lists/imdb/db.sqlite3 Binary files differdiff --git a/users/wpcarro/todo-lists/imdb/imdb-top-250.org b/users/wpcarro/todo-lists/imdb/imdb-top-250.org new file mode 100644 index 000000000000..58a52392cae0 --- /dev/null +++ b/users/wpcarro/todo-lists/imdb/imdb-top-250.org @@ -0,0 +1,256 @@ +# A few years ago, I set a goal to watch every movie on IMDb.com's "Top 250" +# movies list. The list changes frequently, so I took a snapshot of it so that +# I wouldn't be trying to hit a moving target. +# +# Here is my progress thus far: +* IMDB Top 250 +** DONE The Shawshank Redemption +** DONE The Godfather +** DONE The Dark Knight +** DONE The Godfather: Part II +** DONE The Lord of the Rings: The Return of the King +** DONE Pulp Fiction +** DONE Schindler's List +** DONE The Good, the Bad and the Ugly +** DONE 12 Angry Men +** DONE Inception +** DONE Fight Club +** DONE The Lord of the Rings: The Fellowship of the Ring +** DONE Forrest Gump +** DONE The Lord of the Rings: The Two Towers +** DONE The Matrix +** DONE Goodfellas +** TODO Star Wars: Episode V - The Empire Strikes Back +** DONE One Flew Over the Cuckoo's Nest +** DONE Seven Samurai +** DONE Interstellar +** DONE City of God +** TODO Spirited Away +** DONE Saving Private Ryan +** DONE The Green Mile +** DONE Life Is Beautiful +** DONE The Usual Suspects +** DONE Se7en +** DONE Leon +** DONE The Silence of the Lambs +** TODO Star Wars: Episode IV - A New Hope +** DONE It's a Wonderful Life +** DONE Andhadhun +** DONE Dangal +** DONE Spider-Man: Into the Spider-Verse +** TODO Avengers: Infinity War +** DONE Whiplash +** DONE Untouchable +** DONE The Prestige +** DONE The Departed +** DONE The Pianist +** DONE Memento +** DONE Gladiator +** DONE American History X +** DONE The Lion King +** DONE Terminator 2: Judgment Day +** DONE Cinema Paradiso +** DONE Grave of the Fireflies +** DONE Back to the Future +** DONE Indiana Jones and the Raiders of the Lost Ark +** DONE Apocalypse Now +** TODO Alien +** DONE Once Upon a Time in the West +** DONE Psycho +** DONE Rear Window +** DONE Casablanca +** TODO The Great Dictator +** TODO Modern Times +** TODO City Lights +** TODO Kimi no na wa. +** DONE Coco +** DONE Django Unchained +** DONE The Dark Knight Rises +** DONE 3 Idiots +** TODO Taare Zameen Par +** DONE WALLยทE +** TODO Babam ve Oglum +** DONE The Lives of Others +** DONE Old boy +** DONE American Beauty +** DONE Princess Mononoke +** DONE Braveheart +** TODO Aliens +** DONE Once Upon a Time in America +** TODO Das Boot +** DONE The Shining +** DONE Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb +** TODO Witness for the Prosecution +** DONE Paths of Glory +** TODO Sunset Blvd. +** DONE Green Book +** DONE The Hunt +** DONE Jodaeiye Nader az Simin +** DONE Incendies +** DONE Toy Story 3 +** DONE Inglourious Basterds +** DONE Eternal Sunshine of the Spotless Mind +** DONE Amelie +** DONE Snatch +** DONE Requiem for a Dream +** TODO Neon Genesis Evangelion: The End of Evangelion +** DONE L.A. Confidential +** DONE Good Will Hunting +** TODO Bacheha-Ye aseman +** TODO Eskiya +** DONE Toy Story +** DONE Reservoir Dogs +** DONE Full Metal Jacket +** DONE Amadeus +** DONE Scarface +** TODO Star Wars: Episode VI - Return of the Jedi +** DONE Taxi Driver +** DONE Monty Python and the Holy Grail +** DONE The Sting +** DONE A Clockwork Orange +** DONE 2001: A Space Odyssey +** TODO For a Few Dollars More +** TODO To Kill a Mockingbird +** TODO Lawrence of Arabia +** TODO Yojimbo +** DONE The Apartment +** TODO North by Northwest +** DONE Vertigo +** TODO Singin' in the Rain +** TODO Ikiru +** TODO Rashomon +** TODO All About Eve +** TODO Bicycle Thieves +** TODO Double Indemnity +** TODO Citizen Kane +** TODO M +** TODO Metropolis +** TODO The Kid +** DONE Three Billboards Outside Ebbing, Missouri +** DONE Room +** TODO PK +** DONE Inside Out +** DONE El secreto de sus ojos +** DONE Warrior +** DONE Up +** DONE The Wolf of Wall Street +** DONE There Will Be Blood +** DONE Pan's Labyrinth +** DONE V for Vendetta +** TODO Rang De Basanti +** DONE Batman Begins +** DONE Downfall +** TODO Howl's Moving Castle +** DONE A Beautiful Mind +** DONE Lock, Stock and Two Smoking Barrels +** DONE Trainspotting +** DONE Heat +** DONE Casino +** DONE Unforgiven +** TODO Indiana Jones and the Last Crusade +** DONE My Neighbour Totoro +** DONE Die Hard +** TODO Come and See +** TODO Ran +** DONE Blade Runner +** DONE Raging Bull +** TODO The Elephant Man +** DONE Chinatown +** TODO Andrei Rublev +** DONE The Great Escape +** TODO Judgment at Nuremberg +** TODO Some Like It Hot +** TODO Wild Strawberries +** TODO The Seventh Seal +** TODO The Bridge on the River Kwai +** TODO On the Waterfront +** TODO Dial M for Murder +** TODO Tokyo Story +** TODO The Third Man +** TODO The Treasure of the Sierra Madre +** TODO Mr. Smith Goes to Washington +** TODO Gone with the Wind +** TODO Sunrise: A Song of Two Humans +** TODO The General +** TODO The Gold Rush +** TODO Sherlock Jr. +** DONE The Handmaiden +** DONE Logan +** TODO Relatos salvajes +** DONE The Grand Budapest Hotel +** DONE Gone Girl +** DONE Hacksaw Ridge +** TODO 12 Years a Slave +** DONE Guardians of the Galaxy +** DONE Rush +** DONE Spotlight +** TODO Song of the Sea +** TODO The Help +** DONE Prisoners +** DONE Mad Max: Fury Road +** DONE Gran Torino +** TODO Harry Potter and the Deathly Hallows: Part 2 +** DONE Shutter Island +** DONE Hachi: A Dog's Tale +** DONE Mary and Max +** DONE How to Train Your Dragon +** DONE Into the Wild +** DONE No Country for Old Men +** DONE Million Dollar Baby +** DONE Hotel Rwanda +** TODO Before Sunset +** TODO Memories of Murder +** DONE Kill Bill: Vol. 1 +** DONE Finding Nemo +** DONE Catch Me If You Can +** TODO Donnie Darko +** DONE Amores Perros +** DONE Monsters, Inc. +** DONE The Sixth Sense +** DONE The Truman Show +** DONE The Big Lebowski +** TODO In the Mood for Love +** DONE Fargo +** TODO La Haine +** TODO Before Sunrise +** TODO Three Colours: Red +** DONE Jurassic Park +** DONE In the Name of the Father +** DONE Dead Poets Society +** TODO Akira +** DONE The Princess Bride +** TODO Laputa: Castle in the Sky +** DONE Stand by Me +** DONE Platoon +** TODO Paris, Texas +** TODO Nausicaa of the Valley of the Wind +** DONE The Thing +** TODO Gandhi +** TODO Fanny and Alexander +** TODO Stalker +** DONE Life of Brian +** DONE The Deer Hunter +** TODO Rocky +** TODO Network +** TODO Barry Lyndon +** TODO Butch Cassidy and the Sundance Kid +** DONE Cool Hand Luke +** TODO Persona +** TODO The 400 Blows +** TODO Ben-Hur +** TODO The Nights of Cabiria +** TODO Les Diaboliques +** TODO The Wages of Fear +** TODO The Best Years of Our Lives +** TODO The Maltese Falcon +** TODO Rebecca +** TODO The Grapes of Wrath +** TODO It Happened One Night +** TODO La passion de Jeanne d'Arc +** DONE Pirates of the Caribbean: The Curse of the Black Pearl +** DONE Groundhog Day +** DONE Beauty and the Beast +** DONE The Terminator +** DONE Jaws +** DONE The Exorcist +** DONE The Wizard of Oz diff --git a/users/wpcarro/todo-lists/imdb/scratch.sql b/users/wpcarro/todo-lists/imdb/scratch.sql new file mode 100644 index 000000000000..6835c73bd87c --- /dev/null +++ b/users/wpcarro/todo-lists/imdb/scratch.sql @@ -0,0 +1,65 @@ +-- which directors appear most often +SELECT director, COUNT(*) +FROM Movies +GROUP BY director +ORDER BY COUNT(*) DESC +LIMIT 10; + +-- top-rated, most recent movies +SELECT * +FROM ( + SELECT * + FROM Movies + ORDER BY rating DESC + LIMIT 20 +) +ORDER BY YEAR DESC; + +-- top-rated, most recent movies (ignore foreign) +SELECT * +FROM ( + SELECT * + FROM Movies + WHERE requiresSubtitles = 0 + ORDER BY rating DESC + LIMIT 20 +) +ORDER BY YEAR DESC; + +-- most recent movies +SELECT * +FROM Movies +ORDER BY YEAR DESC +LIMIT 15; + +-- most recent movies (ignore foreign) +SELECT * +FROM Movies +WHERE requiresSubtitles = 0 +ORDER BY YEAR DESC +LIMIT 10; + +-- only cartoons +SELECT * +FROM Movies +WHERE isCartoon = true; + +-- only cartoons (ignore foreign) +SELECT * +FROM Movies +WHERE isCartoon = true AND requiresSubtitles = false; + +-- show the movies from the directors that show up on the list more than once. +SELECT * +FROM Movies +WHERE director in ( + SELECT director + FROM ( + SELECT director, COUNT(*) as num + FROM Movies + GROUP BY director + HAVING num > 1 + ORDER BY num DESC + ) +) +ORDER BY director, rating DESC, year DESC; diff --git a/users/wpcarro/todo-lists/paul-graham-essays.org b/users/wpcarro/todo-lists/paul-graham-essays.org new file mode 100644 index 000000000000..7cddcef478d8 --- /dev/null +++ b/users/wpcarro/todo-lists/paul-graham-essays.org @@ -0,0 +1,190 @@ +# I'd like to read all of Paul Graham's essays. I cannot rely on my web browser +# to tell me which I've already read, so I'm resorting to an org file. +* TODO How to Write Usefully +* DONE Being a Noob +* TODO Haters +* TODO The Two Kinds of Moderate +* TODO Fashionable Problems +* TODO Having Kids +* DONE The Lesson to Unlearn +* TODO Novelty and Heresy +* TODO The Bus Ticket Theory of Genius +* TODO General and Surprising +* DONE Charisma / Power +* TODO The Risk of Discovery +* TODO How to Make Pittsburgh a Startup Hub +* TODO Life is Short +* TODO Economic Inequality +* TODO The Refragmentation +* TODO Jessica Livingston +* TODO A Way to Detect Bias +* TODO Write Like You Talk +* TODO Default Alive or Default Dead? +* TODO Why It's Safe for Founders to Be Nice +* TODO Change Your Name +* TODO What Microsoft Is this the Altair Basic of? +* TODO The Ronco Principle +* TODO What Doesn't Seem Like Work? +* TODO Don't Talk to Corp Dev +* TODO Let the Other 95% of Great Programmers In +* TODO How to Be an Expert in a Changing World +* TODO How You Know +* TODO The Fatal Pinch +* DONE Mean People Fail +* TODO Before the Startup +* TODO How to Raise Money +* TODO Investor Herd Dynamics +* TODO How to Convince Investors +* TODO Do Things that Don't Scale +* TODO Startup Investing Trends +* TODO How to Get Startup Ideas +* TODO The Hardware Renaissance +* TODO Startup = Growth +* TODO Black Swan Farming +* TODO The Top of My Todo List +* TODO Writing and Speaking +* TODO How Y Combinator Started +* TODO Defining Property +* TODO Frighteningly Ambitious Startup Ideas +* TODO A Word to the Resourceful +* TODO Schlep Blindness +* TODO Snapshot: Viaweb, June 1998 +* TODO Why Startup Hubs Work +* TODO The Patent Pledge +* TODO Subject: Airbnb +* TODO Founder Control +* TODO Tablets +* TODO What We Look for in Founders +* TODO The New Funding Landscape +* TODO Where to See Silicon Valley +* TODO High Resolution Fundraising +* TODO What Happened to Yahoo +* TODO The Future of Startup Funding +* TODO The Acceleration of Addictiveness +* TODO The Top Idea in Your Mind +* TODO How to Lose Time and Money +* TODO Organic Startup Ideas +* TODO Apple's Mistake +* TODO What Startups Are Really Like +* TODO Persuade xor Discover +* TODO Post-Medium Publishing +* TODO The List of N Things +* TODO The Anatomy of Determination +* TODO What Kate Saw in Silicon Valley +* TODO The Trouble with the Segway +* TODO Ramen Profitable +* DONE Maker's Schedule, Manager's Schedule +* TODO A Local Revolution? +* TODO Why Twitter is a Big Deal +* TODO The Founder Visa +* TODO Five Founders +* TODO Relentlessly Resourceful +* TODO How to Be an Angel Investor +* TODO Why TV Lost +* TODO Can You Buy a Silicon Valley? Maybe. +* TODO What I've Learned from Hacker News +* TODO Startups in 13 Sentences +* TODO Keep Your Identity Small +* TODO After Credentials +* TODO Could VC be a Casualty of the Recession? +* TODO The High-Res Society +* TODO The Other Half of "Artists Ship" +* TODO Why to Start a Startup in a Bad Economy +* TODO A Fundraising Survival Guide +* TODO The Pooled-Risk Company Management Company +* TODO Cities and Ambition +* TODO Disconnecting Distraction +* TODO Lies We Tell Kids +* TODO Be Good +* TODO Why There Aren't More Googles +* TODO Some Heroes +* TODO How to Disagree +* TODO You Weren't Meant to Have a Boss +* TODO A New Venture Animal +* TODO Trolls +* TODO Six Principles for Making New Things +* TODO Why to Move to a Startup Hub +* TODO The Future of Web Startups +* TODO How to Do Philosophy +* TODO News from the Front +* TODO How Not to Die +* TODO Holding a Program in One's Head +* TODO Stuff +* TODO The Equity Equation +* TODO An Alternative Theory of Unions +* TODO The Hacker's Guide to Investors +* TODO Two Kinds of Judgement +* TODO Microsoft is Dead +* TODO Why to Not Not Start a Startup +* TODO Is It Worth Being Wise? +* TODO Learning from Founders +* TODO How Art Can Be Good +* TODO The 18 Mistakes That Kill Startups +* TODO A Student's Guide to Startups +* TODO How to Present to Investors +* TODO Copy What You Like +* TODO The Island Test +* TODO The Power of the Marginal +* TODO Why Startups Condense in America +* TODO How to Be Silicon Valley +* TODO The Hardest Lessons for Startups to Learn +* TODO See Randomness +* TODO Are Software Patents Evil? +* TODO 6,631,372 +* TODO Why YC +* TODO How to Do What You Love +* TODO Good and Bad Procrastination +* TODO Web 2.0 +* TODO How to Fund a Startup +* TODO The Venture Capital Squeeze +* TODO Ideas for Startups +* TODO What I Did this Summer +* TODO Inequality and Risk +* TODO After the Ladder +* TODO What Business Can Learn from Open Source +* TODO Hiring is Obsolete +* TODO The Submarine +* TODO Why Smart People Have Bad Ideas +* TODO Return of the Mac +* DONE Writing, Briefly +* TODO Undergraduation +* TODO A Unified Theory of VC Suckage +* TODO How to Start a Startup +* TODO What You'll Wish You'd Known +* TODO Made in USA +* TODO It's Charisma, Stupid +* TODO Bradley's Ghost +* TODO A Version 1.0 +* TODO What the Bubble Got Right +* TODO The Age of the Essay +* TODO The Python Paradox +* TODO Great Hackers +* TODO Mind the Gap +* TODO How to Make Wealth +* TODO The Word "Hacker" +* TODO What You Can't Say +* TODO Filters that Fight Back +* TODO Hackers and Painters +* TODO If Lisp is So Great +* TODO The Hundred-Year Language +* TODO Why Nerds are Unpopular +* TODO Better Bayesian Filtering +* TODO Design and Research +* TODO A Plan for Spam +* TODO Revenge of the Nerds +* TODO Succinctness is Power +* TODO What Languages Fix +* DONE Taste for Makers +* TODO Why Arc Isn't Especially Object-Oriented +* TODO What Made Lisp Different +* TODO The Other Road Ahead +* TODO The Roots of Lisp +* DONE Five Questions about Language Design +* DONE Being Popular +* DONE Java's Cover +* DONE Beating the Averages +* DONE Lisp for Web-Based Applications +* TODO Chapter 1 of Ansi Common Lisp +* TODO Chapter 2 of Ansi Common Lisp +* DONE Programming Bottom-Up +* DONE This Year We Can End the Death Penalty in California diff --git a/users/wpcarro/todo-lists/travel-hitlist.md b/users/wpcarro/todo-lists/travel-hitlist.md new file mode 100644 index 000000000000..058ff6b274af --- /dev/null +++ b/users/wpcarro/todo-lists/travel-hitlist.md @@ -0,0 +1,83 @@ +# Hit List + +A crude journal of cities I have visited and cities I would like to visit. + +# Europe +* ~~Berlin, Germany~~ +* ~~Hamburg, Germany~~ +* Munich, Germany +* Heidelberg, Germany +* ~~Geneva, Switzerland~~ +* Bern, Switzerland +* Zurich, Switzerland +* Lausanne, Switzerland +* ~~Grenoble, France~~ +* ~~Lyons, France~~ +* ~~Paris, France~~ +* ~~Aix-en-Provence, France~~ +* ~~Bordeaux, France~~ +* Monaco, France +* ~~Ibiza, Spain~~ +* ~~Formentera, Spain~~ +* Barcelona, Spain +* ~~Lisbon, Portugal~~ +* ~~Lagos, Portugal~~ +* ~~Rome, Italy~~ +* ~~Venice, Italy~~ +* Cinque Terre, Italy +* Milan, Italy +* Florence, Italy +* Oslo, Norway +* Bergen, Norway +* Copenhagen, Denmark +* Reykjavik, Iceland +* Stockholm, Sweden +* Gothenburg, Sweden +* ~~Amsterdam, Netherlands~~ +* Dubrovnik, Croatia +* Split, Croatia +* Lake Bled, Slovenia +* Santorini, Greece +* Vienna, Austria +* Salzburg, Austria +* Hallstatt, Austria +* St. Petersburg, Russia +* ~~London, England~~ +* Cambridge, England +* Chester, England +* Edinburgh, Scotland +* ~~Dublin, Ireland~~ +* Galway, Ireland +* Luxembourg, Luxembourg +* Cappadocia, Turkey +* Istanbul, Turkey +* Ankara, Turkey + +# North and South America +* Montreal, Canada +* Quebec City, Canada +* Vancouver, Canada +* Oahu Hawaii, USA +* Chicago, USA +* New Orleans, USA +* Mexico City, Mexico +* Cabo San Lucas, Mexico +* Rio de Janerio, Brazil +* Cartegena, Colombia + +# Asia / Pacific +* Gold Coast, Australia +* Sydney, Australia +* Auckland, New Zealand +* Kohphiphi Islands, Thailand +* Hong Kong, China +* Shanghai, China +* Xitang, China +* Tokyo, Japan +* Kyoto, Japan +* Seoul, South Korea + +# Middle East +* Jaffa, Israel +* Tel Aviv, Israel +* Beirut, Lebanon diff --git a/users/wpcarro/tools/monzo_ynab/.envrc b/users/wpcarro/tools/monzo_ynab/.envrc new file mode 100644 index 000000000000..f368d0b7e813 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/.envrc @@ -0,0 +1,8 @@ +source_up +use_nix +export monzo_client_id="$(jq -j '.monzo | .clientId' < ~/briefcase/secrets.json)" +export monzo_client_secret="$(jq -j '.monzo | .clientSecret' < ~/briefcase/secrets.json)" +export ynab_personal_access_token="$(jq -j '.ynab | .personalAccessToken' < ~/briefcase/secrets.json)" +export ynab_account_id="$(jq -j '.ynab | .accountId' < ~/briefcase/secrets.json)" +export ynab_budget_id="$(jq -j '.ynab | .budgetId' < ~/briefcase/secrets.json)" +export store_path="$(pwd)" diff --git a/users/wpcarro/tools/monzo_ynab/.gitignore b/users/wpcarro/tools/monzo_ynab/.gitignore new file mode 100644 index 000000000000..e92078303bec --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/.gitignore @@ -0,0 +1,3 @@ +/ynab/fixture.json +/monzo/fixture.json +/kv.json diff --git a/users/wpcarro/tools/monzo_ynab/README.md b/users/wpcarro/tools/monzo_ynab/README.md new file mode 100644 index 000000000000..4ccbb35d8c5d --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/README.md @@ -0,0 +1,41 @@ +# monzo_ynab + +Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB) account. YNAB +unfortunately doesn't currently offer an Monzo integration. As a workaround and +a practical excuse to learn Go, I decided to write one myself. + +This job is going to run N times per 24 hours. Monzo offers webhooks for +reacting to certain types of events. I don't expect I'll need realtime data for +my YNAB integration. That may change, however, so it's worth noting. + +## Installation + +Like many other packages in this repository, `monzo_ynab` is packaged using +Nix. To install and use, you have two options: + +You can install using `nix-build` and then run the resulting +`./result/bin/monzo_ynab`. + +```shell +> nix-build . && ./result/bin/monzo_ynab +``` + +Or you can install using `nix-env` if you'd like to create the `monzo_ynab` +symlink. + +```shell +> nix-env -f ~/briefcase/monzo_ynab -i +``` + +## Deployment + +While this project is currently not deployed, my plan is to host it on Google +Cloud and run it as a Cloud Run application. What I don't yet know is whether or +not this is feasible or a good idea. One complication that I foresee is that the +OAuth 2.0 login flow requires a web browser until the access token and refresh +tokens are acquired. I'm unsure how to workaround this at the moment. + +For more information about the general packaging and deployment strategies I'm +currently using, refer to the [deployments][deploy] writeup. + +[deploy]: ../deploy/README.md diff --git a/users/wpcarro/tools/monzo_ynab/auth.go b/users/wpcarro/tools/monzo_ynab/auth.go new file mode 100644 index 000000000000..b66bacb10687 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/auth.go @@ -0,0 +1,101 @@ +package auth + +//////////////////////////////////////////////////////////////////////////////// +// Dependencies +//////////////////////////////////////////////////////////////////////////////// + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "os" + "os/exec" + "utils" +) + +//////////////////////////////////////////////////////////////////////////////// +// Constants +//////////////////////////////////////////////////////////////////////////////// + +var ( + BROWSER = os.Getenv("BROWSER") + REDIRECT_URI = "http://localhost:8080/authorization-code" +) + +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// + +// This is the response returned from Monzo when we exchange our authorization +// code for an access token. While Monzo returns additional fields, I'm only +// interested in AccessToken and RefreshToken. +type accessTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` +} + +type Tokens struct { + AccessToken string + RefreshToken string + ExpiresIn int +} + +//////////////////////////////////////////////////////////////////////////////// +// Functions +//////////////////////////////////////////////////////////////////////////////// + +// Returns the access token and refresh tokens for the Monzo API. +func GetTokensFromAuthCode(authCode string, clientID string, clientSecret string) *Tokens { + res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{ + "grant_type": {"authorization_code"}, + "client_id": {clientID}, + "client_secret": {clientSecret}, + "redirect_uri": {REDIRECT_URI}, + "code": {authCode}, + }) + utils.FailOn(err) + defer res.Body.Close() + payload := &accessTokenResponse{} + json.NewDecoder(res.Body).Decode(payload) + + return &Tokens{payload.AccessToken, payload.RefreshToken, payload.ExpiresIn} +} + +// Open a web browser to allow the user to authorize this application. Return +// the authorization code sent from Monzo. +func GetAuthCode(clientID string) string { + // TODO(wpcarro): Consider generating a random string for the state when the + // application starts instead of hardcoding it here. + state := "xyz123" + url := fmt.Sprintf( + "https://auth.monzo.com/?client_id=%s&redirect_uri=%s&response_type=code&state=%s", + clientID, REDIRECT_URI, state) + exec.Command(BROWSER, url).Start() + + authCode := make(chan string) + go func() { + log.Fatal(http.ListenAndServe(":8080", + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // 1. Get authorization code from Monzo. + if req.URL.Path == "/authorization-code" { + params := req.URL.Query() + reqState := params["state"][0] + code := params["code"][0] + + if reqState != state { + log.Fatalf("Value for state returned by Monzo does not equal our state. %s != %s", reqState, state) + } + authCode <- code + + fmt.Fprintf(w, "Authorized!") + } else { + log.Printf("Unhandled request: %v\n", *req) + } + }))) + }() + result := <-authCode + return result +} diff --git a/users/wpcarro/tools/monzo_ynab/job.nix b/users/wpcarro/tools/monzo_ynab/job.nix new file mode 100644 index 000000000000..1e10751012e2 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/job.nix @@ -0,0 +1,12 @@ +{ depot, briefcase, ... }: + +depot.buildGo.program { + name = "job"; + srcs = [ + ./main.go + ]; + deps = with briefcase.gopkgs; [ + kv + utils + ]; +} diff --git a/users/wpcarro/tools/monzo_ynab/main.go b/users/wpcarro/tools/monzo_ynab/main.go new file mode 100644 index 000000000000..06f1944eab70 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/main.go @@ -0,0 +1,43 @@ +// Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB) +// account. YNAB unfortunately doesn't currently offer an Monzo integration. As +// a workaround and a practical excuse to learn Go, I decided to write one +// myself. +// +// This job is going to run N times per 24 hours. Monzo offers webhooks for +// reacting to certain types of events. I don't expect I'll need realtime data +// for my YNAB integration. That may change, however, so it's worth noting. + +package main + +import ( + "fmt" +) + +var ( + ynabAccountID = os.Getenv("ynab_account_id") +) + +//////////////////////////////////////////////////////////////////////////////// +// Business Logic +//////////////////////////////////////////////////////////////////////////////// + +// Convert a Monzo transaction struct, `tx`, into a YNAB transaction struct. +func toYnab(tx monzoSerde.Transaction) ynabSerde.Transaction { + return ynabSerde.Transaction{ + Id: tx.Id, + Date: tx.Created, + Amount: tx.Amount, + Memo: tx.Notes, + AccountId: ynabAccountID, + } +} + +func main() { + txs := monzo.TransactionsLast24Hours() + var ynabTxs []ynabSerde.Transaction{} + for tx := range txs { + append(ynabTxs, toYnab(tx)) + } + ynab.PostTransactions(ynabTxs) + os.Exit(0) +} diff --git a/users/wpcarro/tools/monzo_ynab/monzo/client.go b/users/wpcarro/tools/monzo_ynab/monzo/client.go new file mode 100644 index 000000000000..8c6c41e29f40 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/monzo/client.go @@ -0,0 +1,52 @@ +package monzoClient + +import ( + "fmt" + "log" + "monzoSerde" + "net/http" + "net/url" + "strings" + "time" + "tokens" + "utils" +) + +const ( + accountID = "pizza" +) + +type Client struct{} + +// Ensure that the token server is running and return a new instance of a Client +// struct. +func Create() *Client { + tokens.StartServer() + time.Sleep(time.Second * 1) + return &Client{} +} + +// Returns a slice of transactions from the last 24 hours. +func (c *Client) Transactions24Hours() []monzoSerde.Transaction { + token := tokens.AccessToken() + form := url.Values{"account_id": {accountID}} + client := http.Client{} + req, _ := http.NewRequest("POST", "https://api.monzo.com/transactions", + strings.NewReader(form.Encode())) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("User-Agent", "monzo-ynab") + res, err := client.Do(req) + + utils.DebugRequest(req) + utils.DebugResponse(res) + + if err != nil { + utils.DebugRequest(req) + utils.DebugResponse(res) + log.Fatal(err) + } + defer res.Body.Close() + + return []monzoSerde.Transaction{} +} diff --git a/users/wpcarro/tools/monzo_ynab/monzo/serde.go b/users/wpcarro/tools/monzo_ynab/monzo/serde.go new file mode 100644 index 000000000000..a38585eca632 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/monzo/serde.go @@ -0,0 +1,82 @@ +// This package hosts the serialization and deserialization logic for all of the +// data types with which our application interacts from the Monzo API. +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" +) + +type TxMetadata struct { + FasterPayment string `json:"faster_payment"` + FpsPaymentId string `json:"fps_payment_id"` + Insertion string `json:"insertion"` + Notes string `json:"notes"` + Trn string `json:"trn"` +} + +type TxCounterparty struct { + AccountNumber string `json:"account_number"` + Name string `json:"name"` + SortCode string `json:"sort_code"` + UserId string `json:"user_id"` +} + +type Transaction struct { + Id string `json:"id"` + Created time.Time `json:"created"` + Description string `json:"description"` + Amount int `json:"amount"` + Currency string `json:"currency"` + Notes string `json:"notes"` + Metadata TxMetadata + AccountBalance int `json:"account_balance"` + International interface{} `json:"international"` + Category string `json:"category"` + IsLoad bool `json:"is_load"` + Settled time.Time `json:"settled"` + LocalAmount int `json:"local_amount"` + LocalCurrency string `json:"local_currency"` + Updated time.Time `json:"updated"` + AccountId string `json:"account_id"` + UserId string `json:"user_id"` + Counterparty TxCounterparty `json:"counterparty"` + Scheme string `json:"scheme"` + DedupeId string `json:"dedupe_id"` + Originator bool `json:"originator"` + IncludeInSpending bool `json:"include_in_spending"` + CanBeExcludedFromBreakdown bool `json:"can_be_excluded_from_breakdown"` + CanBeMadeSubscription bool `json:"can_be_made_subscription"` + CanSplitTheBill bool `json:"can_split_the_bill"` + CanAddToTab bool `json:"can_add_to_tab"` + AmountIsPending bool `json:"amount_is_pending"` + // Fees interface{} `json:"fees"` + // Merchant interface `json:"merchant"` + // Labels interface{} `json:"labels"` + // Attachments interface{} `json:"attachments"` + // Categories interface{} `json:"categories"` +} + +// Attempts to encode a Monzo transaction struct into a string. +func serializeTx(tx *Transaction) (string, error) { + x, err := json.Marshal(tx) + return string(x), err +} + +// Attempts to parse a string encoding a transaction presumably sent from a +// Monzo server. +func deserializeTx(x string) (*Transaction, error) { + target := &Transaction{} + err := json.Unmarshal([]byte(x), target) + return target, err +} + +func main() { + b, _ := ioutil.ReadFile("./fixture.json") + tx := string(b) + target, _ := deserializeTx(tx) + out, _ := serializeTx(target) + fmt.Println(out) +} diff --git a/users/wpcarro/tools/monzo_ynab/requests.txt b/users/wpcarro/tools/monzo_ynab/requests.txt new file mode 100644 index 000000000000..2da17c0b326a --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/requests.txt @@ -0,0 +1,80 @@ +################################################################################ +# YNAB +################################################################################ +:ynab = https://api.youneedabudget.com/v1 +:ynab-access-token := (getenv "ynab_personal_access_token") +:ynab-budget-id := (getenv "ynab_budget_id") +:ynab-account-id := (getenv "ynab_account_id") + +# Test +GET :ynab/budgets +Authorization: Bearer :ynab-access-token + +# List transactions +GET :ynab/budgets/:ynab-budget-id/transactions +Authorization: Bearer :ynab-access-token + +# Post transactions +POST :ynab/budgets/:ynab-budget-id/transactions +Authorization: Bearer :ynab-access-token +Content-Type: application/json +{ + "transactions": [ + { + "account_id": ":ynab-account-id", + "date": "2019-12-30", + "amount": 10000, + "payee_name": "Richard Stallman", + "memo": "Not so free software after all...", + "cleared": "cleared", + "approved": true, + "flag_color": "red", + "import_id": "xyz-123" + } + ] +} + +################################################################################ +# Monzo +################################################################################ +:monzo = https://api.monzo.com +:monzo-access-token := (getenv "monzo_cached_access_token") +:monzo-refresh-token := (getenv "monzo_cached_refresh_token") +:monzo-client-id := (getenv "monzo_client_id") +:monzo-client-secret := (getenv "monzo_client_secret") +:monzo-account-id := (getenv "monzo_account_id") + +# List transactions +GET :monzo/transactions +Authorization: Bearer :monzo-access-token +account_id==:monzo-account-id + +# Refresh access token +# According from the docs, the access token expires in 6 hours. +POST :monzo/oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer :monzo-access-token +grant_type=refresh_token&client_id=:monzo-client-id&client_secret=:monzo-client-secret&refresh_token=:monzo-refresh-token + +################################################################################ +# Tokens server +################################################################################ +:tokens = http://localhost:4242 + +# Get tokens +GET :tokens/tokens + +# Get application state for debugging purposes +GET :tokens/state + +# Force refresh tokens +POST :tokens/refresh-tokens + +# Set tokens +POST :tokens/set-tokens +Content-Type: application/json +{ + "access_token": "access-token", + "refresh_token": "refresh-token", + "expires_in": 120 +} diff --git a/users/wpcarro/tools/monzo_ynab/shell.nix b/users/wpcarro/tools/monzo_ynab/shell.nix new file mode 100644 index 000000000000..910d7c1829e2 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = [ + pkgs.go + pkgs.goimports + pkgs.godef + ]; +} diff --git a/users/wpcarro/tools/monzo_ynab/tokens.go b/users/wpcarro/tools/monzo_ynab/tokens.go new file mode 100644 index 000000000000..4be967ccb803 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/tokens.go @@ -0,0 +1,283 @@ +// Creating a Tokens server to manage my access and refresh tokens. Keeping this +// as a separate server allows me to develop and use the access tokens without +// going through client authorization. +package main + +//////////////////////////////////////////////////////////////////////////////// +// Dependencies +//////////////////////////////////////////////////////////////////////////////// + +import ( + "auth" + "encoding/json" + "fmt" + "io" + "kv" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "syscall" + "time" + "utils" +) + +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// + +// This is the response from Monzo's API after we request an access token +// refresh. +type refreshTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ClientId string `json:"client_id"` + ExpiresIn int `json:"expires_in"` +} + +// This is the shape of the request from clients wishing to set state of the +// server. +type setTokensRequest struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` +} + +// This is our application state. +type state struct { + accessToken string `json:"access_token"` + refreshToken string `json:"refresh_token"` +} + +type readMsg struct { + sender chan state +} + +type writeMsg struct { + state state + sender chan bool +} + +type channels struct { + reads chan readMsg + writes chan writeMsg +} + +//////////////////////////////////////////////////////////////////////////////// +// Top-level Definitions +//////////////////////////////////////////////////////////////////////////////// + +var chans = &channels{ + reads: make(chan readMsg), + writes: make(chan writeMsg), +} + +var ( + monzoClientId = os.Getenv("monzo_client_id") + monzoClientSecret = os.Getenv("monzo_client_secret") + storePath = os.Getenv("store_path") +) + +//////////////////////////////////////////////////////////////////////////////// +// Utils +//////////////////////////////////////////////////////////////////////////////// + +// Print the access and refresh tokens for debugging. +func logTokens(access string, refresh string) { + log.Printf("Access: %s\n", access) + log.Printf("Refresh: %s\n", refresh) +} + +func (state *state) String() string { + return fmt.Sprintf("state{\n\taccessToken: \"%s\",\n\trefreshToken: \"%s\"\n}\n", state.accessToken, state.refreshToken) +} + +// Schedule a token refresh for `expiresIn` seconds using the provided +// `refreshToken`. This will update the application state with the access token +// and schedule an additional token refresh for the newly acquired tokens. +func scheduleTokenRefresh(expiresIn int, refreshToken string) { + duration := time.Second * time.Duration(expiresIn) + timestamp := time.Now().Local().Add(duration) + // TODO(wpcarro): Consider adding a more human readable version that will + // log the number of hours, minutes, etc. until the next refresh. + log.Printf("Scheduling token refresh for %v\n", timestamp) + time.Sleep(duration) + log.Println("Refreshing tokens now...") + accessToken, refreshToken := refreshTokens(refreshToken) + log.Println("Successfully refreshed tokens.") + logTokens(accessToken, refreshToken) + setState(accessToken, refreshToken) +} + +// Exchange existing credentials for a new access token and `refreshToken`. Also +// schedule the next refresh. This function returns the newly acquired access +// token and refresh token. +func refreshTokens(refreshToken string) (string, string) { + // TODO(wpcarro): Support retries with exponential backoff. + res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{ + "grant_type": {"refresh_token"}, + "client_id": {monzoClientId}, + "client_secret": {monzoClientSecret}, + "refresh_token": {refreshToken}, + }) + if res.StatusCode != http.StatusOK { + // TODO(wpcarro): Considering panicking here. + utils.DebugResponse(res) + } + if err != nil { + utils.DebugResponse(res) + log.Fatal("The request to Monzo to refresh our access token failed.", err) + } + defer res.Body.Close() + payload := &refreshTokenResponse{} + err = json.NewDecoder(res.Body).Decode(payload) + if err != nil { + log.Fatal("Could not decode the JSON response from Monzo.", err) + } + + go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken) + + // Interestingly, JSON decoding into the refreshTokenResponse can success + // even if the decoder doesn't populate any of the fields in the + // refreshTokenResponse struct. From what I read, it isn't possible to make + // these fields as required using an annotation, so this guard must suffice + // for now. + if payload.AccessToken == "" || payload.RefreshToken == "" { + log.Fatal("JSON parsed correctly but failed to populate token fields.") + } + + return payload.AccessToken, payload.RefreshToken +} + +func persistTokens(access string, refresh string) { + log.Println("Persisting tokens...") + kv.Set(storePath, "monzoAccessToken", access) + kv.Set(storePath, "monzoRefreshToken", refresh) + log.Println("Successfully persisted tokens.") +} + +// Listen for SIGINT and SIGTERM signals. When received, persist the access and +// refresh tokens and shutdown the server. +func handleInterrupts() { + // Gracefully handle interruptions. + sigs := make(chan os.Signal, 1) + done := make(chan bool) + + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + go func() { + sig := <-sigs + log.Printf("Received signal to shutdown. %v\n", sig) + state := getState() + persistTokens(state.accessToken, state.refreshToken) + done <- true + }() + + <-done + log.Println("Exiting...") + os.Exit(0) +} + +// Set `accessToken` and `refreshToken` on application state. +func setState(accessToken string, refreshToken string) { + msg := writeMsg{state{accessToken, refreshToken}, make(chan bool)} + chans.writes <- msg + <-msg.sender +} + +// Return our application state. +func getState() state { + msg := readMsg{make(chan state)} + chans.reads <- msg + return <-msg.sender +} + +//////////////////////////////////////////////////////////////////////////////// +// Main +//////////////////////////////////////////////////////////////////////////////// + +func main() { + // Manage application state. + go func() { + state := &state{} + for { + select { + case msg := <-chans.reads: + log.Println("Reading from state...") + log.Println(state) + msg.sender <- *state + case msg := <-chans.writes: + log.Println("Writing to state.") + log.Printf("Old: %s\n", state) + *state = msg.state + log.Printf("New: %s\n", state) + // As an attempt to maintain consistency between application + // state and persisted state, everytime we write to the + // application state, we will write to the store. + persistTokens(state.accessToken, state.refreshToken) + msg.sender <- true + } + } + }() + + // Retrieve cached tokens from store. + accessToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoAccessToken")) + refreshToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoRefreshToken")) + + log.Println("Attempting to retrieve cached credentials...") + logTokens(accessToken, refreshToken) + + if accessToken == "" || refreshToken == "" { + log.Println("Cached credentials are absent. Authorizing client...") + authCode := auth.GetAuthCode(monzoClientId) + tokens := auth.GetTokensFromAuthCode(authCode, monzoClientId, monzoClientSecret) + setState(tokens.AccessToken, tokens.RefreshToken) + go scheduleTokenRefresh(tokens.ExpiresIn, tokens.RefreshToken) + } else { + setState(accessToken, refreshToken) + // If we have tokens, they may be expiring soon. We don't know because + // we aren't storing the expiration timestamp in the state or in the + // store. Until we have that information, and to be safe, let's refresh + // the tokens. + go scheduleTokenRefresh(0, refreshToken) + } + + // Gracefully handle shutdowns. + go handleInterrupts() + + // Listen to inbound requests. + fmt.Println("Listening on http://localhost:4242 ...") + log.Fatal(http.ListenAndServe(":4242", + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/refresh-tokens" && req.Method == "POST" { + state := getState() + go scheduleTokenRefresh(0, state.refreshToken) + fmt.Fprintf(w, "Done.") + } else if req.URL.Path == "/set-tokens" && req.Method == "POST" { + // Parse + payload := &setTokensRequest{} + err := json.NewDecoder(req.Body).Decode(payload) + if err != nil { + log.Fatal("Could not decode the user's JSON request.", err) + } + + // Update application state + setState(payload.AccessToken, payload.RefreshToken) + + // Refresh tokens + go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken) + + // Ack + fmt.Fprintf(w, "Done.") + } else if req.URL.Path == "/state" && req.Method == "GET" { + // TODO(wpcarro): Ensure that this returns serialized state. + w.Header().Set("Content-type", "application/json") + state := getState() + payload, _ := json.Marshal(state) + io.WriteString(w, string(payload)) + } else { + log.Printf("Unhandled request: %v\n", *req) + } + }))) +} diff --git a/users/wpcarro/tools/monzo_ynab/tokens.nix b/users/wpcarro/tools/monzo_ynab/tokens.nix new file mode 100644 index 000000000000..97de09d741e9 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/tokens.nix @@ -0,0 +1,23 @@ +{ depot, briefcase, ... }: + +let + auth = depot.buildGo.package { + name = "auth"; + srcs = [ + ./auth.go + ]; + deps = with briefcase.gopkgs; [ + utils + ]; + }; +in depot.buildGo.program { + name = "token-server"; + srcs = [ + ./tokens.go + ]; + deps = with briefcase.gopkgs; [ + kv + utils + auth + ]; +} diff --git a/users/wpcarro/tools/monzo_ynab/ynab/client.go b/users/wpcarro/tools/monzo_ynab/ynab/client.go new file mode 100644 index 000000000000..0492b9071adc --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/ynab/client.go @@ -0,0 +1,24 @@ +package client + +import ( + "serde" +) + +// See requests.txt for more details. +func PostTransactions(accountID string, txs []serde.Transaction{}) error { + return map[string]string{ + "transactions": [ + { + "account_id": accountID, + "date": "2019-12-30", + "amount": 10000, + "payee_name": "Richard Stallman", + "memo": "Not so free software after all...", + "cleared": "cleared", + "approved": true, + "flag_color": "red", + "import_id": "xyz-123" + } + ] + } +} diff --git a/users/wpcarro/tools/monzo_ynab/ynab/serde.go b/users/wpcarro/tools/monzo_ynab/ynab/serde.go new file mode 100644 index 000000000000..53dd33e83637 --- /dev/null +++ b/users/wpcarro/tools/monzo_ynab/ynab/serde.go @@ -0,0 +1,52 @@ +// This package hosts the serialization and deserialization logic for all of the +// data types with which our application interacts from the YNAB API. +package main + +import ( + "encoding/json" + "fmt" + "time" +) + +type Transaction struct { + Id string `json:"id"` + Date time.Time `json:"date"` + Amount int `json:"amount"` + Memo string `json:"memo"` + Cleared string `json:"cleared"` + Approved bool `json:"approved"` + FlagColor string `json:"flag_color"` + AccountId string `json:"account_id"` + AccountName string `json:"account_name"` + PayeeId string `json:"payeed_id"` + PayeeName string `json:"payee_name"` + CategoryId string `json:"category_id"` + CategoryName string `json:"category_name"` + Deleted bool `json:"deleted"` + // TransferAccountId interface{} `json:"transfer_account_id"` + // TransferTransactionId interface{} `json:"transfer_transaction_id"` + // MatchedTransactionId interface{} `json:"matched_transaction_id"` + // ImportId interface{} `json:"import_id"` + // Subtransactions interface{} `json:"subtransactions"` +} + +// Attempts to encode a YNAB transaction into a string. +func serializeTx(tx *Transaction) (string, error) { + x, err := json.Marshal(tx) + return string(x), err +} + +// Attempts to parse a string encoding a transaction presumably sent from a +// YNAB server. +func deserializeTx(x string) (*Transaction, error) { + target := &Transaction{} + err := json.Unmarshal([]byte(x), target) + return target, err +} + +func main() { + target, _ := deserializeTx(tx) + out, _ := serializeTx(target) + fmt.Println(out) + fmt.Println(ynabOut) +} diff --git a/users/wpcarro/tools/rfcToKindle/LICENSE b/users/wpcarro/tools/rfcToKindle/LICENSE new file mode 100644 index 000000000000..7a4a3ea2424c --- /dev/null +++ b/users/wpcarro/tools/rfcToKindle/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/users/wpcarro/tools/rfcToKindle/README.md b/users/wpcarro/tools/rfcToKindle/README.md new file mode 100644 index 000000000000..e7b4fa841ef6 --- /dev/null +++ b/users/wpcarro/tools/rfcToKindle/README.md @@ -0,0 +1,30 @@ +# rfcToKindle + +Wirelessly transfer RFC documents to your Kindle to device for an alternative +medium for reading. + +## Installation + +`rfcToKindle` makes use of [`buildGo.nix`][2] to package itself. If you're +using [Nix][1], you can install `rfcToKindle` using `nix-env`: + +```shell +> nix-env -f https://github.com/wpcarro/rfcToKindle -i +``` + +## Usage + +```shell +> rfcToKindle -document rfc6479 -recipient username@kindle.com +``` + +## Dependencies + +This uses `sendgmr` to send the file to the Kindle. Make sure: +1. That `sendgmr` is installed and available on $PATH. +2. That it is configured to work with your preferred email address. +3. That the email address `sendgmr` is configured to use is whitelisted in + your Kindle "Personal Document Settings". + +[1]: https://nixos.org/nix/ +[2]: https://git.tazj.in/tree/nix/buildGo diff --git a/users/wpcarro/tools/rfcToKindle/default.nix b/users/wpcarro/tools/rfcToKindle/default.nix new file mode 100644 index 000000000000..8fb93c3bb5b8 --- /dev/null +++ b/users/wpcarro/tools/rfcToKindle/default.nix @@ -0,0 +1,11 @@ +{ depot, ... }: + +# TODO: This doesn't depend on `sendgmr` at the moment, but it should. As such, +# it's an imcomplete packaging. +depot.buildGo.program { + name = "rfcToKindle"; + srcs = [ + ./main.go + ]; + deps = []; +} diff --git a/users/wpcarro/tools/rfcToKindle/main.go b/users/wpcarro/tools/rfcToKindle/main.go new file mode 100644 index 000000000000..0f4f2dd9ec4f --- /dev/null +++ b/users/wpcarro/tools/rfcToKindle/main.go @@ -0,0 +1,89 @@ +// Author: wpcarro@gmail.com +// +// Wirelessly transfer RFC documents to your Kindle to device for an alternative +// medium for reading. +// +// Usage: +// ```shell +// > go run rfcToKindle.go -document rfc6479 -recipient username@kindle.com +// ``` +// +// This uses `sendgmr` to send the file to the Kindle. Make sure: +// 1. That `sendgmr` is installed and available on $PATH. +// 2. That it is configured to work with your preferred email address. +// 3. That the email address `sendgmr` is configured to use is whitelisted in +// your Kindle "Personal Document Settings". + +package main + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "strings" +) + +func main() { + document := flag.String("document", "", "(Required) The name of the document to fetch. For example \"RFC6479\".") + recipient := flag.String("recipient", "", "(Required) The email address of the Kindle device.") + subject := flag.String("subject", "", "(Optional) The email address of the Kindle device.") + flag.Parse() + + if *document == "" { + // TODO: Is log.Fatal the best function to use here? + log.Fatal("-document cannot be empty. See -help for more information.") + } + + if *recipient == "" { + log.Fatal("-recipient cannot be empty. See -help for more information.") + } + + *document = strings.ToLower(*document) + + url := fmt.Sprintf("https://www.ietf.org/rfc/%s.txt", *document) + resp, err := http.Get(url) + fmt.Printf("Downloading %s ... ", url) + + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + f, err := ioutil.TempFile("", fmt.Sprintf("%s-*.txt", *document)) + if err != nil { + log.Fatal(err) + } + // TODO: Verify if this is cleaning up or not. + defer os.Remove(f.Name()) + + _, err = io.Copy(f, resp.Body) + if err != nil { + log.Fatal(err) + } + fmt.Println("done.") + + if *subject == "" { + *subject = fmt.Sprintf("%s - Sent from rfcToKindle.go", *document) + } + + // Although I couldn't find it documented anywhere, the email sent to the + // Kindle must have a body, even if the body isn't used for anything. + fmt.Printf("Emailing %s to %s ... ", f.Name(), *recipient) + cmd := exec.Command("sendgmr", + fmt.Sprintf("--to=%s", *recipient), + fmt.Sprintf("--body_file=%s", f.Name()), + fmt.Sprintf("--subject=%s", *subject), + fmt.Sprintf("--attachment_files=%s", f.Name())) + err = cmd.Run() + if err != nil { + log.Fatal(err) + } + fmt.Println("done.") + + os.Exit(0) +} diff --git a/users/wpcarro/tools/run/.envrc b/users/wpcarro/tools/run/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/tools/run/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/tools/run/README.md b/users/wpcarro/tools/run/README.md new file mode 100644 index 000000000000..d3cccecf910c --- /dev/null +++ b/users/wpcarro/tools/run/README.md @@ -0,0 +1,30 @@ +# run + +Simplify the commands you call to run scripts on the command line. + +```shell +> run path/to/file.py +> run path/to/file.ts +``` + +## How? + +Define a run.json configuration mapping commands to filename extensions like +so: +```json +{ + ".ts": "npx ts-node $file", + ".py": "python3 $file" +} +``` + +Then call `run path/to/some/file.ts` on the command line, and `npx ts-node +file.ts` will run. + +## Installation + +Install `run` using Nix. + +```shell +> nix-env -iA briefcase.run +``` diff --git a/users/wpcarro/tools/run/default.nix b/users/wpcarro/tools/run/default.nix new file mode 100644 index 000000000000..7d772c3f9079 --- /dev/null +++ b/users/wpcarro/tools/run/default.nix @@ -0,0 +1,11 @@ +{ pkgs, depot, briefcase, ... }: + +depot.buildGo.program { + name = "run"; + srcs = [ + ./main.go + ]; + deps = with briefcase.gopkgs; [ + utils + ]; +} diff --git a/users/wpcarro/tools/run/main.go b/users/wpcarro/tools/run/main.go new file mode 100644 index 000000000000..04906ece91f7 --- /dev/null +++ b/users/wpcarro/tools/run/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "utils" +) + +func main() { + if len(os.Args) != 2 { + log.Fatal("You can only call run with a single file at a time.") + } + + rulesPath := utils.Resolve("run.json", []string{"/home/wpcarro/.config/run/run.json"}) + b, err := ioutil.ReadFile(rulesPath) + if err != nil { + log.Fatal("Could not locate a run.json file: ", err) + } + rules := map[string]string{} + err = json.Unmarshal(b, &rules) + if err != nil { + log.Fatal("Could not decode run.json as JSON: ", err) + } + + fileName := os.Args[1] + ext := filepath.Ext(fileName) + cmd, ok := rules[ext] + + if !ok { + log.Fatalf("No rules for extension, %s, have been defined.", ext) + } + + // TODO(wpcarro): Support more sophisticated parsing than just string + // splitting. To handle 'cases like this'. + tokens := strings.Split(strings.Replace(cmd, "$file", fileName, 1), " ") + c := exec.Command(tokens[0], tokens[1:]...) + err = c.Start() + // TODO(wpcarro): Forward STDERR and STDOUT. + if err != nil { + log.Fatal(err) + } + fmt.Println(c.Wait()) +} diff --git a/users/wpcarro/tools/run/shell.nix b/users/wpcarro/tools/run/shell.nix new file mode 100644 index 000000000000..e14bffae487c --- /dev/null +++ b/users/wpcarro/tools/run/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + go + goimports + godef + ]; +} diff --git a/users/wpcarro/tools/simple_vim/config.vim b/users/wpcarro/tools/simple_vim/config.vim new file mode 100644 index 000000000000..ea40964ee803 --- /dev/null +++ b/users/wpcarro/tools/simple_vim/config.vim @@ -0,0 +1,98 @@ +" My barebones vimrc without any Vundle dependencies. +" +" I'm attempting to optimize the following: +" - Minimize dependencies +" - Maximize ergonomics +" - Maximize Tmux compatibility +" - Minimize shadowing of existing Vim KBDs +" +" Warning: This is currently unstable as it is a work-in-progress. +" +" Author: William Carroll <wpcarro@gmail.com> + +" Use <Space> as the leader key. +let mapleader = " " +nnoremap <leader>ev :tabnew<CR>:edit ~/.vimrc<CR> +nnoremap <leader>sv :source ~/.vimrc<CR> +nnoremap <leader>w :w<CR> +nnoremap <leader>h :help + +" increment,decrement numbers +nnoremap + <C-a> +" TODO: Restore with better KBD +" nnoremap - <C-x> + +" Visit the CWD +nnoremap - :e .<CR> + +" Turn line numbers on. +set number + +" Easily create vertical, horizontal window splits. +nnoremap sh :vsplit<CR> +nnoremap sj :split<CR>:wincmd j<CR> +nnoremap sk :split<CR> +nnoremap sl :vsplit<CR>:wincmd l<CR> + +" Move across window splits. +" TODO: Change to <M-{h,j,k,l}>. +nnoremap <C-h> :wincmd h<CR> +nnoremap <C-j> :wincmd j<CR> +nnoremap <C-k> :wincmd k<CR> +nnoremap <C-l> :wincmd l<CR> + +" TODO: Support these. +" nnoremap <M-q> :q<CR> +" nnoremap <M-h> :wincmd h<CR> +" nnoremap <M-j> :wincmd j<CR> +" nnoremap <M-k> :wincmd k<CR> +" nnoremap <M-l> :wincmd l<CR> + +" Use <Enter> instead of G to support: +" 20<Enter> - to jump to line 20 +" d20<Enter> - to delete from the current line until line 20 +" <C-v>20<Enter> - to select from the current line until line 20 +nnoremap <Enter> G +onoremap <Enter> G +vnoremap <Enter> G + +" Easily change modes on keyboards that don't have CapsLock mapped to <Esc> +inoremap jk <ESC> + +" CRUD tabs. +nnoremap <TAB> :tabnext<CR> +nnoremap <S-TAB> :tabprevious<CR> +nnoremap <C-t> :tabnew<CR>:edit .<CR> +nnoremap <C-w> :tabclose<CR> +" TODO: Re-enable these once <M-{h,j,k,l}> are supported. +" nnoremap <C-l> :+tabmove<CR> +" nnoremap <C-h> :-tabmove<CR> + +" Use H,L to goto beggining,end of a line. +" Swaps the keys to ensure original functionality of H,L are preserved. +nnoremap H ^ +nnoremap L $ +nnoremap ^ H +nnoremap $ L + +" Use H,L in visual mode too +vnoremap H ^ +vnoremap L $ +vnoremap ^ H +vnoremap $ L + +" Emacs hybrid mode +" TODO: model this after tpope's rsi.vim (Readline-style insertion) +cnoremap <C-g> <C-c> +cnoremap <C-a> <C-b> +inoremap <C-a> <C-o>^ +inoremap <C-e> <C-o>$ +inoremap <C-b> <C-o>h +inoremap <C-f> <C-o>l + +" Indenting +" The following three settings are based on option 2 of `:help tabstop` +set tabstop=4 +set shiftwidth=4 +set expandtab +set autoindent diff --git a/users/wpcarro/tools/simple_vim/default.nix b/users/wpcarro/tools/simple_vim/default.nix new file mode 100644 index 000000000000..f8f965f2c024 --- /dev/null +++ b/users/wpcarro/tools/simple_vim/default.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: + +let + configVim = builtins.path { + path = ./config.vim; + name = "config.vim"; + }; + + script = pkgs.writeShellScriptBin "simple_vim" '' + ${pkgs.vim}/bin/vim -u ${configVim} + ''; +in pkgs.stdenv.mkDerivation { + name = "simple_vim"; + buildInputs = [ script ]; +} diff --git a/users/wpcarro/tools/symlinkManager/README.md b/users/wpcarro/tools/symlinkManager/README.md new file mode 100644 index 000000000000..b0fc58c8e989 --- /dev/null +++ b/users/wpcarro/tools/symlinkManager/README.md @@ -0,0 +1,14 @@ +# Dotfile Symlink Manager + +Find and delete all symlinks to the dotfiles defined in `$BRIEFCASE`. + +Oftentimes I corrupt the state of my configuration files. The intention with +this script is to help me clean things up when this happens. An example workflow +might look like: + +```shell +> symlink-mgr --audit +> symlink-mgr --seriously +> briefcase # changes directory to $BRIEFCASE +> make install +``` diff --git a/users/wpcarro/tools/symlinkManager/default.nix b/users/wpcarro/tools/symlinkManager/default.nix new file mode 100644 index 000000000000..16bb26bb3c2e --- /dev/null +++ b/users/wpcarro/tools/symlinkManager/default.nix @@ -0,0 +1,11 @@ +{ depot, briefcase, ... }: + +depot.buildGo.program { + name = "symlink-mgr"; + srcs = [ + ./main.go + ]; + deps = with briefcase.gopkgs; [ + utils + ]; +} diff --git a/users/wpcarro/tools/symlinkManager/main.go b/users/wpcarro/tools/symlinkManager/main.go new file mode 100644 index 000000000000..e682867fb850 --- /dev/null +++ b/users/wpcarro/tools/symlinkManager/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "utils" +) + +var hostnames = map[string]string{ + os.Getenv("DESKTOP"): "desktop", + os.Getenv("LAPTOP"): "work_laptop", +} + +func main() { + audit := flag.Bool("audit", false, "Output all symlinks that would be deleted. This is the default behavior. This option is mutually exclusive with the --seriously option.") + seriously := flag.Bool("seriously", false, "Actually delete the symlinks. This option is mutually exclusive with the --audit option.") + repoName := flag.String("repo-name", "briefcase", "The name of the repository.") + deviceOnly := flag.Bool("device-only", false, "Only output the device-specific dotfiles.") + flag.Parse() + + if !*audit && !*seriously { + log.Fatal(errors.New("Either -audit or -seriously needs to be set.")) + } + if *audit == *seriously { + log.Fatal(errors.New("Arguments -audit and -seriously are mutually exclusive")) + } + + home, err := os.UserHomeDir() + utils.FailOn(err) + count := 0 + + err = filepath.Walk(home, func(path string, info os.FileInfo, err error) error { + if utils.IsSymlink(info.Mode()) { + dest, err := os.Readlink(path) + utils.FailOn(err) + + var predicate func(string) bool + + if *deviceOnly { + predicate = func(dest string) bool { + var hostname string + hostname, err = os.Hostname() + utils.FailOn(err) + seeking, ok := hostnames[hostname] + if !ok { + log.Fatal(fmt.Sprintf("Hostname \"%s\" not supported in the hostnames map.", hostname)) + } + return strings.Contains(dest, *repoName) && strings.Contains(dest, seeking) + } + } else { + predicate = func(dest string) bool { + return strings.Contains(dest, *repoName) + } + } + + if predicate(dest) { + if *audit { + fmt.Printf("%s -> %s\n", path, dest) + } else if *seriously { + fmt.Printf("rm %s\n", path) + err = os.Remove(path) + utils.FailOn(err) + } + count += 1 + } + } + return nil + }) + utils.FailOn(err) + if *audit { + fmt.Printf("Would have deleted %d symlinks.\n", count) + } else if *seriously { + fmt.Printf("Successfully deleted %d symlinks.\n", count) + } + + os.Exit(0) +} diff --git a/users/wpcarro/tools/url-blocker/.envrc b/users/wpcarro/tools/url-blocker/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/tools/url-blocker/Main.hs b/users/wpcarro/tools/url-blocker/Main.hs new file mode 100644 index 000000000000..926412ce91f9 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/Main.hs @@ -0,0 +1,205 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE DeriveGeneric #-} +module Main ( main ) where + +-------------------------------------------------------------------------------- +-- Dependencies +-------------------------------------------------------------------------------- + +import qualified Data.Maybe as Maybe +import qualified Data.Time.Clock as Clock +import qualified Data.Time.Calendar as Calendar +import qualified Data.Time.LocalTime as LocalTime +import qualified Data.ByteString.Lazy as LazyByteString +import qualified Data.Aeson as Aeson +import qualified Data.Either.Combinators as Either +import qualified Data.HashMap.Strict as HashMap +import qualified Data.Text as Text +import qualified Data.Text.IO as TextIO +import qualified Data.Text.Read as TextRead +import qualified Data.List as List + +import GHC.Generics +import Data.Aeson ((.:)) +import Data.Text (Text) + +-------------------------------------------------------------------------------- +-- Types +-------------------------------------------------------------------------------- + +newtype URL = URL { getURL :: Text } deriving (Show, Eq, Generic) + +newtype IPAddress = IPAddress { getIPAddress :: Text } deriving (Show) + +newtype Domain = Domain { getDomain :: Text } deriving (Show) + +newtype Hour = Hour { getHour :: Int } deriving (Show, Eq, Generic) + +newtype Minute = Minute { getMinute :: Int } deriving (Show, Eq, Generic) + +data EtcHostsEntry = EtcHostsEntry { ip :: IPAddress + , domains :: [Domain] + } deriving (Show) + +-- | Write these in terms of your system's local time (i.e. `date`). +data TimeSlot = TimeSlot { beg :: (Hour, Minute) + , end :: (Hour, Minute) + } deriving (Show, Eq, Generic) + +data Allowance = Allowance { day :: Calendar.DayOfWeek + , timeslots :: [TimeSlot] + } deriving (Show, Eq, Generic) + +data Rule = Rule { urls :: [URL] + , allowed :: [Allowance] + } deriving (Show, Eq, Generic) + +-------------------------------------------------------------------------------- +-- Instances +-------------------------------------------------------------------------------- + +instance Aeson.FromJSON TimeSlot where + parseJSON = Aeson.withText "timeslot" $ \x -> do + let [a, b] = Text.splitOn "-" x + [ah, am] = Text.splitOn ":" a + [bh, bm] = Text.splitOn ":" b + case extractTimeSlot ah am bh bm of + Left s -> fail s + Right x -> pure x + where + extractTimeSlot :: Text -> Text -> Text -> Text -> Either String TimeSlot + extractTimeSlot ah am bh bm = do + (begh, _) <- TextRead.decimal ah + (begm, _) <- TextRead.decimal am + (endh, _) <- TextRead.decimal bh + (endm, _) <- TextRead.decimal bm + pure $ TimeSlot{ beg = (Hour begh, Minute begm) + , end = (Hour endh, Minute endm) + } + +instance Aeson.FromJSON Allowance where + parseJSON = Aeson.withObject "allowance" $ \x -> do + day <- x .: "day" + timeslots <- x .: "timeslots" + pure $ Allowance{day, timeslots} + +instance Aeson.FromJSON URL where + parseJSON = Aeson.withText "URL" $ \x -> do + pure $ URL { getURL = x } + +instance Aeson.FromJSON Rule where + parseJSON = Aeson.withObject "rule" $ \x -> do + urls <- x .: "urls" + allowed <- x .: "allowed" + pure Rule{urls, allowed} + +-------------------------------------------------------------------------------- +-- Functions +-------------------------------------------------------------------------------- + +-- | Pipe operator +(|>) :: a -> (a -> b) -> b +(|>) a f = f a +infixl 1 |> + +-- | Returns True if the current time falls within any of the `timeslots`. +isWithinTimeSlot :: LocalTime.LocalTime -> [TimeSlot] -> Bool +isWithinTimeSlot date timeslots = + List.any withinTimeSlot timeslots + where + withinTimeSlot :: TimeSlot -> Bool + withinTimeSlot TimeSlot{ beg = (Hour ah, Minute am) + , end = (Hour bh, Minute bm) + } = + let LocalTime.TimeOfDay{LocalTime.todHour, LocalTime.todMin} = + LocalTime.localTimeOfDay date + in (todHour > ah) && (todMin > am) && (todHour < bh) && (todMin < bm) + +-- | Returns True if `day` is the same day as today. +isToday :: LocalTime.LocalTime -> Calendar.DayOfWeek -> Bool +isToday date day = today == day + where + today = Calendar.dayOfWeek (LocalTime.localDay date) + +-- | Returns True if a list of none of the `allowances` are valid. +shouldBeBlocked :: LocalTime.LocalTime -> [Allowance] -> Bool +shouldBeBlocked _ [] = True +shouldBeBlocked date allowances = do + case filter (isToday date . day) allowances of + [Allowance{timeslots}] -> not $ isWithinTimeSlot date timeslots + [] -> True + -- Error when more than one rule per day + _ -> True + +-- | Maps an EtcHostsEntry to the line of text url-blocker will append to /etc/hosts. +serializeEtcHostEntry :: EtcHostsEntry -> Text +serializeEtcHostEntry EtcHostsEntry{ip, domains} = + (getIPAddress ip) <> "\t" <> (Text.unwords $ fmap getDomain domains) + +-- | Create an EtcHostsEntry mapping the URLs in `rule` to 127.0.0.1 if the +-- URLs should be blocked. +maybeBlockURL :: LocalTime.LocalTime -> Rule -> Maybe EtcHostsEntry +maybeBlockURL date Rule{urls, allowed} = + if shouldBeBlocked date allowed then + Just $ EtcHostsEntry { ip = IPAddress "127.0.0.1" + , domains = fmap (Domain . getURL) urls + } + else + Nothing + +-- | Read and parse the rules.json file. +-- TODO(wpcarro): Properly handle errors for file not found. +-- TODO(wpcarro): Properly handle errors for parse failures. +-- TODO(wpcarro): How can we resolve the $HOME directory when this is run as +-- root? +getRules :: IO [Rule] +getRules = do + contents <- LazyByteString.readFile "/home/wpcarro/.config/url-blocker/rules.json" + let payload = Aeson.eitherDecode contents + pure $ Either.fromRight [] payload + +-- | Informational header added to /etc/hosts before the entries that +-- url-blocker adds. +urlBlockerHeader :: Text +urlBlockerHeader = + Text.unlines [ "################################################################################" + , "# Added by url-blocker." + , "#" + , "# Warning: url-blocker will remove anything that you add beneath this header." + , "################################################################################" + ] + +-- | Removes all entries that url-blocker may have added to /etc/hosts. +removeURLBlockerEntries :: Text -> Text +removeURLBlockerEntries etcHosts = + case Text.breakOn urlBlockerHeader etcHosts of + (etcHosts', _) -> etcHosts' + +-- | Appends the newly created `entries` to `etcHosts`. +addURLBlockerEntries :: Text -> Text -> Text +addURLBlockerEntries entries etcHosts = + Text.unlines [ etcHosts + , urlBlockerHeader + , entries + ] + +-- | This script reads the current /etc/hosts, removes any entries that +-- url-blocker may have added in a previous run, and adds new entries to block +-- URLs according to the rules.json file. +main :: IO () +main = do + rules <- getRules + tz <- LocalTime.getCurrentTimeZone + ct <- Clock.getCurrentTime + let date = LocalTime.utcToLocalTime tz ct + entries = rules + |> fmap (maybeBlockURL date) + |> Maybe.catMaybes + |> fmap serializeEtcHostEntry + |> Text.unlines + existingEtcHosts <- TextIO.readFile "/etc/hosts" + existingEtcHosts + |> removeURLBlockerEntries + |> addURLBlockerEntries entries + |> \x -> writeFile "/etc/hosts" (Text.unpack x) diff --git a/users/wpcarro/tools/url-blocker/README.md b/users/wpcarro/tools/url-blocker/README.md new file mode 100644 index 000000000000..1b7fea8c15e0 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/README.md @@ -0,0 +1,47 @@ +# url-blocker + +`url-blocker` blocks the URLs that you want to block when you want it to block +them. + +Let's say that you don't want to visit Twitter during the work week. Create the +file `~/.config/url-blocker/rules.json` with the following contents and +`url-blocker` will take care of the rest. + +```json +# ~/.config/url-blocker/rules.json +[ + { + "urls": [ + "twitter.com", + "www.twitter.com", + ], + "allowed": [ + { + "day": "Saturday", + "timeslots": [ + "00:00-11:59" + ] + }, + { + "day": "Sunday", + "timeslots": [ + "00:00-11:59" + ] + } + ] + } +] +``` + +## Installation + +```shell +$ nix-env -iA 'briefcase.tools.url-blocker' +``` + +## How does it work? + +`systemd` is intended to run `url-blocker` once every minute. `url-blocker` will +read `/etc/hosts` and map the URLs defined in `rules.json` to `127.0.0.1` when +you want them blocked. Because `systemd` run once every minute, `/etc/hosts` +should be current to the minute as well. diff --git a/users/wpcarro/tools/url-blocker/default.nix b/users/wpcarro/tools/url-blocker/default.nix new file mode 100644 index 000000000000..943644e5f542 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/default.nix @@ -0,0 +1,33 @@ +{ pkgs, ... }: + +let + ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: [ + hpkgs.time + hpkgs.aeson + hpkgs.either + ]); + + # This is the systemd service unit + service = pkgs.stdenv.mkDerivation { + name = "url-blocker"; + src = builtins.path { path = ./.; name = "url-blocker"; }; + buildPhase = '' + ${ghc}/bin/ghc Main.hs + ''; + installPhase = '' + mv ./Main $out + ''; + }; + + # This is the systemd timer unit. + # Run once every minute. + # Give root privilege. + systemdUnit = { + systemd = { + timers.simple-timer = { + wantedBy = [ "timers.target" ]; + partOf = []; + }; + }; + }; +in null diff --git a/users/wpcarro/tools/url-blocker/rules.json b/users/wpcarro/tools/url-blocker/rules.json new file mode 100644 index 000000000000..95e4dc9a90c1 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/rules.json @@ -0,0 +1,28 @@ +[ + { + "urls": [ + "facebook.com", + "www.facebook.com", + "twitter.com", + "www.twitter.com", + "youtube.com", + "www.youtube.com", + "instagram.com", + "www.instagram.com" + ], + "allowed": [] + }, + { + "urls": [ + "chat.googleplex.com" + ], + "allowed": [ + { + "day": "Sunday", + "timeslots": [ + "18:35-18:39" + ] + } + ] + } +] diff --git a/users/wpcarro/tools/url-blocker/shell.nix b/users/wpcarro/tools/url-blocker/shell.nix new file mode 100644 index 000000000000..1adc566c0121 --- /dev/null +++ b/users/wpcarro/tools/url-blocker/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + time + aeson + either + hspec + ]; +} diff --git a/users/wpcarro/utils/README.md b/users/wpcarro/utils/README.md new file mode 100644 index 000000000000..0724dff2a992 --- /dev/null +++ b/users/wpcarro/utils/README.md @@ -0,0 +1,8 @@ +# Nix Utils + +Nix is useful and well-designed in many cases. Nix's standard library of is not +a representative example of the value of Nix. I'm hoping to replace some of the +standard library functions with versions that I find more intuitive and +ergonomic to consume. + +This directcory is where I will host that work. diff --git a/users/wpcarro/utils/builder.nix b/users/wpcarro/utils/builder.nix new file mode 100644 index 000000000000..45e783cf0ba8 --- /dev/null +++ b/users/wpcarro/utils/builder.nix @@ -0,0 +1,11 @@ +{ pkgs, ... }: + +let + inherit (pkgs) writeShellScriptBin; +in { + # Create a derivation that creates an executable shell script named `as` that + # calls the program located at `path`, forwarding all of the arguments. + wrapNonNixProgram = { path, as }: writeShellScriptBin as '' + exec ${path} "$@" + ''; +} diff --git a/users/wpcarro/utils/default.nix b/users/wpcarro/utils/default.nix new file mode 100644 index 000000000000..db01702a1fec --- /dev/null +++ b/users/wpcarro/utils/default.nix @@ -0,0 +1,14 @@ +args@{ pkgs, ... }: + +# This top-level module exposes all of my utility functions for Nix. It should +# be used like: +# ```nix +# inherit (briefcase.utils) fs; +# ``` + +let + builder = import ./builder.nix args; + fs = import ./fs.nix args; +in { + inherit builder fs; +} diff --git a/users/wpcarro/utils/fs.nix b/users/wpcarro/utils/fs.nix new file mode 100644 index 000000000000..6305e705b141 --- /dev/null +++ b/users/wpcarro/utils/fs.nix @@ -0,0 +1,41 @@ +{ pkgs, ... }: + +# `fs` contains utility functions for working with the filesystem. + +let + inherit (builtins) attrNames hasAttr map readDir; + inherit (pkgs.lib) filterAttrs; +in { + # Returns a list of all of the regular files in `dir`. + files = dir: + map (name: dir + "/${name}") + (attrNames + (filterAttrs (_: type: type == "regular") (readDir dir))); + + # Returns a list of all of the directories in `dir`. + dirs = dir: + map (name: dir + "/${name}") + (attrNames + (filterAttrs (_: type: type == "directory") (readDir dir))); + + # Returns a list of paths to all of the `name` files starting at `dir`. + find = name: dir: + if hasAttr name (readDir dir) then + [ (dir + name) ] ++ concatMap findAllDefaultNix (dirs dir) + else + concatMap findAllDefaultNix (dirs dir); + + # Looks for `name` in `dir`; if it cannot find it, it checks the parent + # directory. + resolve = name: dir: + if hasAttr name (readDir dir) then + dir + "/${name}" + else + # This prevents the function from infinitely recursing and eventually + # stack overflowing. + if (dirOf dir) == dir then + null + else + resolve name (dirOf dir); + }; +} diff --git a/users/wpcarro/website/README.md b/users/wpcarro/website/README.md new file mode 100644 index 000000000000..1267eb01fc5e --- /dev/null +++ b/users/wpcarro/website/README.md @@ -0,0 +1,11 @@ +# wpcarro.dev + +https://wpcarro.dev is my personal website. I expose a few subdomains, one of +which you are probably visiting right now, git.wpcarro.dev. Here are some of +the others: + +- `blog.wpcarro.dev`: My personal blog +- `learn.wpcarro.dev`: Teaching others to code +- `sandbox.wpcarro.dev`: Where I deploy some pet projects and code sketches + +Visit https://wpcarro.dev for a sitemap. diff --git a/users/wpcarro/website/blog/.envrc b/users/wpcarro/website/blog/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/website/blog/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/website/blog/archetypes/default.md b/users/wpcarro/website/blog/archetypes/default.md new file mode 100644 index 000000000000..00e77bd79be4 --- /dev/null +++ b/users/wpcarro/website/blog/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/users/wpcarro/website/blog/config.toml b/users/wpcarro/website/blog/config.toml new file mode 100644 index 000000000000..a0d65815f6a1 --- /dev/null +++ b/users/wpcarro/website/blog/config.toml @@ -0,0 +1,25 @@ +baseURL = "https://blog.wpcarro.dev" +disqusShortname = "blog-wpcarro-dev" +languageCode = "en-us" +title = "blog.wpcarro.dev" +theme = "tailwind" +pygmentsCodeFences = true +pygmentsUseClasses = true + +[taxonomies] + tag = "tags" + +[permalinks] + posts = "/posts/:year/:month/:title/" + +[params] + author = "William Carroll" + description = "Loosely structured streams of consciousness" + tagline = "Loosely structured streams of consciousness" + +[languages] + [languages.en] + contentDir = "content/english" + languageCode = "en-us" + languageName = "English" + weight = 1 \ No newline at end of file diff --git a/users/wpcarro/website/blog/content/english/caffeine.md b/users/wpcarro/website/blog/content/english/caffeine.md new file mode 100644 index 000000000000..9c3dbac0f120 --- /dev/null +++ b/users/wpcarro/website/blog/content/english/caffeine.md @@ -0,0 +1,5 @@ +--- +title: "Caffeine" +date: 2020-03-11T22:50:40Z +draft: true +--- diff --git a/users/wpcarro/website/blog/content/english/cell-phone-experiment.md b/users/wpcarro/website/blog/content/english/cell-phone-experiment.md new file mode 100644 index 000000000000..550ba4865ee0 --- /dev/null +++ b/users/wpcarro/website/blog/content/english/cell-phone-experiment.md @@ -0,0 +1,280 @@ +--- +title: "Cell Phone Experiment" +date: 2020-04-02T02:02:07Z +draft: false +--- + +### TL;DR + +I will not use my cell phone during March to learn more about how much I depend +on it. + +### Explore/Exploit + +Ever since I read Charles Duhigg's book, [The Power of Habit](poh), I try to +habituate as many aspects of my life as I can. + +Making my bed every morning is an example of a habit -- so too is flossing at +night before bed. + +The *exploit* axis of the [explore/exploit tradeoff](exp-exp) endows habits with +their power. Brian Christian and Tom Griffiths explain this concept more clearly +than I can in Chapter 2 of their exceptional book, [Algorithms to Live +By](algos). + +Habits are powerful, but if I overly exploit an activity, I may settle on a +local optimum in lieu of settling on a global optimum; these are the opportunity +costs of exploiting (i.e. habits) versus exploring (i.e. spontaneity). + +But what if it was possible to habituate exploration? + +### Monthly challenges + +Every month since October 2018, I challenge myself to try something new. In the +past, monthly challenges have been things like: +- sign up and take Brazilian Jiu Jitsu classes +- buy a guitar and learn [Freight Train](https://www.youtube.com/watch?v=IUK8emiWabU) +- study Italian +- learn a handstand + +Typically for an activity to qualify as a challenge, I must spend *at least +fifteen minutes* working on it *at least five days* each week. + +This month (i.e. March) I'm challenging myself to avoid using my cell phone. + +My parents gave me a cell phone when when I was a freshman in High School; I was +14 years old. I am now 28, so I have been using a cell phone semi-daily for over +a decade. + +While I enjoy the convenience that my cell phone provides me, I am curious to +suspend my usage to more clearly understand how much I depend on it... + +### April + +Now it is early April, and I completed March's challenge. So how was it? + +Below I outline the parts of using a cell phone that I missed and the parts that +I surprisingly did not miss. I will also mention the two times that I used my +cell phone and why. + +The first three things that I missed all relate to time. + +#### Timekeeping + +On the first day I realized that unless I was near a computer, I did not know +what time it was. + +I exclusively use my cell phone as my watch; I do not wear a watch. To adapt, I +started looking for clocks around my office and while I was taking public +transportation. Thankfully London posts the current time on the digital train +schedules. This oriented me while I was traveling, which was also when I needed +to know the time the most. + +Most of the month, however, I never precisely knew what time it was. + +#### Alarm clocks + +While I anticipated living without an alarm clock prior to the experiment, I +decided against buying a substitute. Prior to this month, I theorized that +morning alarms probably disrupt the quality of my sleep. If I'm tired, shouldn't +I keep sleeping? + +As the month progressed and my 24 hour day morphed into a 25 hour day, I learned +that I would prefer waking up at a set time every day and synchronize my +schedule with the rest of my timezone. + +I am still unsure if alarm clocks are helpful in the long-term. I would have +slept with the curtains drawn to allow the morning sun to wake me +up. Unfortunately, I live on the ground floor nearby a brightly shining street +lamp that spills into my bedroom. + +If I lived somewhere more remote (perhaps even a suburb would do) I would like +to repeat an experiment where I live for a month without an alarm clock. + +For now, I must return to the Temple of Chronology and supplicate until Father +Time restores my sanity. + +#### Timers + +Using timers motivates me to do a bunch of short tasks like cleaning my flat for +fifteen minutes, stretching, or reading before bed. Thankfully, I already owned +a physical timer that I keep in my kitchen. This replaced the timer on my phone +without disrupting my routine. + +#### Maps + +Speaking of being disoriented, what about living without maps software? On the +few occasions where I traveled somewhere that was unfamiliar to me, I had to +memorize the directions from my computer before I departed. + +At least I didn't need to visit gas stations or museums to buy trifold tourist +maps... + +I once left my office mistakenly assuming that I would download the directions +to my destination while commuting. As I awaited the office elevator, I realized +that I had no clue where I was heading. + +Thankfully I wasn't far from the safety, comfort, and familiarity of my desktop +computer -- with its fatty WiFi connection. In no time I was studying Google +Maps in my web browser and memorizing the directions. + +Overall this was hardly an inconvenience, and I think I even enjoyed +stress-testing my memory: a job that I so often outsource to hardware. + +#### Rendezvouses + +A couple of times I met friends in various parts of the city. Organizing these +particular rendezvouses was a novel (read: anachronistic) experience. For all +you young whippersnappers reading, take out your stone tablets and chisels. I'm +going to explain how this works: + +First I would tell my friends where and when to meet me. I emphasized that I +would be quite helpless to any changes they might make to the plans once I began +commuting, which made the commitments unusually more binding. + +On one occasion my friend -- who is characteristically prompt, and even chides +me for when I'm late -- was twenty minutes late for our engagement. My friend is +German, so I figured I should do my civic duty of alerting the German embassy +that my friend had broken German code, is obscenely late, and should therefore +hand-in his passport and renounce his citizenship. After awhile my conscience +advised me to reconsider. + +It was fortunate for both of us that I did not fully understand how late he was. +Remember: I didn't know what time it was. + +I decided this would be a useful opportunity to test my patience, so I loitered +for twenty minutes outside of our meeting point. He couldn't text me to tell me +that he was late. I couldn't listen to music, call family or friends, or partake +in any of the other rituals that modern-day loiterers observe to pass the +time. In the end he showed up, and it was scarcely a big deal. + +This experience made me wonder what the policy for abandoning plans is when +someone is running late. Before smart phones, how long did people wait? Maybe +the proper etiquette is to wait long enough for you to absolve yourself of the +guilt of flaking in the unlikely event that your friend arrives shortly after +you leave. + +So... thirty minutes? I'll call my grandma tomorrow and ask her. + +#### Boredom + +My phone couldn't entertain me while I queued at the grocery store. Same too +when I commuted. + +I also found myself listening to less music than I usually do. I decided to read +to occupy the void when I could; this helped me progress towards completing this +year's [GoodReads challenge][gr-annual]. + +### Cheating + +I used my phone twice during March. + +1. Once to use my bank's mobile app to internationally transfer money from my + U.K. account to my U.S. account. I could have used [TransferWise's][tw] + website, but I didn't. +2. Another time I used my phone to take pictures of an item that I wanted to + sell on [CraigsList][cl]. I could have and perhaps should have used my laptop's + webcam, but at the time, I didn't want to. I am accustomed to using my phone + to take pictures, and I wanted to sell something. + +In both of these cases, prior habits eroded my resolve to stay the course. These +are useful reminders that habits don't distinguish between helpful and hurtful; +they just exist. + +In total I would estimate that I spent somewhere around fifteen minutes using +my phone in March. While not perfect: + +> Better a diamond with a flaw than a pebble without (Confucius) + +### Substitution = Dilution + +While the explicit goal of this challenge was to avoid using my cell phone for a +month, the implicit goal was to disengage from many of the +[nonessential][essentialism] activities that compete for my attention. + +There were some activities that I didn't miss while living without a cell +phone. This wasn't because I don't value these activities, but rather because I +can adequately replace them with alternatives. + +For texting and making phone calls, I used [Telegram][wtf-telegram]. Telegram +helped me sustain a healthy relationship with my girlfriend while still honoring +the constraints of the challenge. + +While I appreciated the convenience Telegram provided, I felt that I remained +about as [available][wtf-availability] during March as I was in February. If I +ever experiment with drastically reducing my availability, I will be more +explicit about my objectives. + +### Distraction displacement (whack-a-mole) + +Because cell phones and other electronics have conditioned my behavior, I +habitually avoid boredom and seek entertainment. On its face this may not sound +like a harmful practice. My generation drills the aphorism "you only live once", +suggesting that we may want to embrace a Hedonistic lifestyle. + +Hedonism may or may not be a wise way to play the game of Life. All I know is +that living a life in which I am often stimulated but proportionately distracted +appeals increasingly less to me as time progresses. + +During March I noticed that once I freed my attention from sending/receiving +texts, my brain quickly reassigned my attention to maintaining a vigil over the +other social media outposts that I maintain. + +I should also admit that I habitually checked Telegram now that it served as my +new cell phone. Didn't see that coming... + +In another case, once I discovered that I could use Instagram in a web browser +instead of on my phone, I filled my newfound time and attention on +[Instagram.com][ig] (don't click!): displacing the time that I spent on an app +on my phone to time that I spent on a website in a web browser. + +Holy whack-a-mole! + +Halfway through the month, I wrote a [program to block websites][url-blocker] on +my computer. Surprisingly this worked and forced me to more deliberately fill +this hard-fought, foreign time with other activities. + +### Easy come, easy go? + +As the saying for making friends goes, "easy come, easy go", implying that +friendships that you easily form can just as easily be destroyed. + +Habits invert this creation/destruction relationship. In my experience "easy +come" implies "difficult to go". + +For example, I could easily form the habit of eating chocolate around 15:00 at +work; curbing this habit would require more effort. When I compare this to the +difficulty I experienced habituating a meditation practice, and how easily I +can dislodge my meditation practice, it seems to me that the laws of habits +dictate "easy come, difficult go; difficult come, easy go". + +I suspect that while my cravings for using a cell phone have temporarily ceased, +they will return shortly after I start using my cell phone. And as if nothing +happened, I return to where I was at the end of February just before I decided +to curb my cell phone usage. + +Because of this, I'm planning on keeping my cell phone in my closet where I +stored it during the month of March. As noted, enough substitutes exist for me +to live a mostly normal life: one where I am not unnecessarily straining the +relationships of my friends and my family. After all these are the people who +matter most to me and those who drive me to explore new ways to improve. + +I recognize that the "self" in self-experimentation is a misnomer. Can you truly +conduct an [N of 1 trial][nof1]? My decisions impact the people in my life, and +I want to thank everyone who tolerates my eccentric and oftentimes annoying +experimentation. + +Thank you for reading. + +[pod]: https://www.goodreads.com/book/show/12609433-the-power-of-habit +[exp-exp]: https://en.wikipedia.org/wiki/Multi-armed_bandit +[algos]: https://www.goodreads.com/book/show/25666050-algorithms-to-live-by +[gr-annual]: https://www.goodreads.com/user_challenges/19737920 +[cl]: http://craigslist.com +[tw]: https://transferwise.com +[url-blocker]: https://github.com/wpcarro/url-blocker +[wtf-telegram]: https://telegram.org +[wtf-availability]: https://landing.google.com/sre/sre-book/chapters/availability-table +[essentialism]: https://www.goodreads.com/book/show/18077875-essentialism +[ig]: https://instagram.com +[nof1]: https://en.wikipedia.org/wiki/N_of_1_trial diff --git a/users/wpcarro/website/blog/content/english/lets-learn-nix-caching.md b/users/wpcarro/website/blog/content/english/lets-learn-nix-caching.md new file mode 100644 index 000000000000..a436d4de25eb --- /dev/null +++ b/users/wpcarro/website/blog/content/english/lets-learn-nix-caching.md @@ -0,0 +1,49 @@ +--- +title: "Lets Learn Nix Caching" +date: 2020-03-17T18:05:38Z +draft: true +--- + +## TL;DR + +1. I use `NixOS/nixpkgs-channels` instead of `NixOS/nixpkgs` and avoid + `nix-channel`. + +## More information + +- By default the Nix package manager uses cache.nixos.org as a binary cache. +- Visit status.nixos.org +- `git clone git@github.com:NixOS/nixpkgs-channels` instead of + `NixOS/nixpkgs`. The former mirrors the latter and uses Git branches to track + the published channels. + +## What is a Nix channel + +If you run... + +```shell +$ git clone git@github.com:NixOS/nixpkgs ~/nixpkgs +$ export NIX_PATH="nixpkgs=$(realpath ~/nixpkgs)" +``` + +One benefit to cloning nixpkgs is that you can browse the source code on your +machine using tools like `git` and `emacs`. You can also experimentally patch +and test Nix code this way. + +If any of the above appeals to you, clone `nixpkgs-channels` instead. + +The Nix maintainers build and test the commits from `nixpkgs` using Hydra. Tests +include reproducibility tests, etc. + +Various channels have different verification phases. + +The cache at cache.nixos.org is populate the cache at cache.nixos.org. + +You want to increase the likelihood that you are hitting this cache. For +example, `google-chrome` takes hours to build. + +## What is a binary cache? + +## What is Hydra (Nix CI)? + +## What is Cachix? diff --git a/users/wpcarro/website/blog/content/english/lets-learn-nix-determinism-vs-reproducibility.md b/users/wpcarro/website/blog/content/english/lets-learn-nix-determinism-vs-reproducibility.md new file mode 100644 index 000000000000..2e12a6b06fcc --- /dev/null +++ b/users/wpcarro/website/blog/content/english/lets-learn-nix-determinism-vs-reproducibility.md @@ -0,0 +1,121 @@ +--- +title: "Lets Learn Nix: Reproducibility" +date: 2020-03-17T12:06:47Z +draft: true +--- + +I am dedicating this page to defining and disambiguating some terminology. I +think it is important to use these terms precisely, so it may be worthwhile to +memorize these definitions and ensure that you are clarifying the discourse +rather than muddying it. + +## Terms + +- repeatable build: +- reproducible build: +- deterministic build: +- pure function: +- impure function: +- idempotent function: + +TODO(wpcarro): Consistently and deliberately use reproducible and +deterministic. + +## Repeatable vs. Reproducible + +Is NixOS reproducible? Visit [@grhmc][who-grhmc]'s website, +[r13y.com](https://r13y.com), to find out. + +At the time of this writing, 1519 of 1568 (i.e. 96.9%) of the paths in the +`nixos.iso_minimal.x86_64-linux` installation image are reproducible. + +## What hinders reproducibility? + +Timestamps. + +If package A encodes a timestamp into its build artifact, then we can +demonstrate that package A is *not reproducible* simply by building it at two +different times and doing a byte-for-byte comparison of the build artifacts. + +## Does Nix protect developers against non-determinism + +Yes. But not entirely. How? + +## Deterministic Nix derivation + +```nix +{ pkgs ? import <nixpkgs> {}, ... }: + +with pkgs; + +stdenv.mkDerivation { + name = "reproducible"; + phases = [ "buildPhase" ]; + buildPhase = "echo reproducible >$out"; +} +``` + +## Non-deterministic Nix derivation + +We can introduce some non-determinism into our build using the `date` function. + +```nix +# file: /tmp/test.nix +{ pkgs ? import <nixpkgs> {}, ... }: + +with pkgs; + +stdenv.mkDerivation { + name = "non-reproducible"; + phases = [ "buildPhase" ]; + buildPhase = "date >$out"; +} +``` + +Then run... + +```shell +$ nix-build /tmp/test.nix +$ nix-build /tmp/test.nix --check --keep-failed +``` + +## How do you test reproducibility? + +We can use `cmp` to compare files byte-for-byte. The following comparison should +fail: + +```shell +$ echo foo >/tmp/a +$ echo bar >/tmp/b +$ cmp --silent /tmp/{a,b} +$ echo $? +``` + +And the following comparison should succeed: + +```shell +$ echo hello >/tmp/a +$ echo hello >/tmp/b +$ cmp --silent /tmp/{a,b} +$ echo $? +``` + +## Reproducible vs. deterministic + +Reproducible builds *are* deterministic builds and deterministic build + +## Deterministic, Reproducible, Pure, Idempotent, oh my + +- A pure function has no side-effects. + +- An idempotent function can be executed more than once with the same arguments + without altering the side-effects. + +- A deterministic function ensures that + +## Deterministic vs. Reproducible + +I can check if a build is reproducible using [these tools][wtf-repro-tools]. + +[wtf-repro-tools]: https://reproducible-builds.org/tools/ +[who-grhmc]: https://twitter.com/grhmc diff --git a/users/wpcarro/website/blog/content/english/lets-learn-nix-dotfiles.md b/users/wpcarro/website/blog/content/english/lets-learn-nix-dotfiles.md new file mode 100644 index 000000000000..084fb19e4406 --- /dev/null +++ b/users/wpcarro/website/blog/content/english/lets-learn-nix-dotfiles.md @@ -0,0 +1,401 @@ +--- +title: "Let's Learn Nix: Dotfiles" +date: 2020-03-13T22:23:02Z +draft: true +--- + +## Let's Learn Nix: Dotfiles + +### Dependencies + +Speaking of dependencies, here's what you should know before reading this tutorial. + +- Basic Nix syntax: Nix 1p + +What version of Nix are we using? What version of `<nixpkgs>` are we using? What +operating system are we using? So many variables... + +Cartesian product of all possibilities... + +TODO(wpcarro): Create a graphic of the options. + +### The problems of dotfiles + +How do you manage your dependencies? + +You can use `stow` to install the dotfiles. + +### home-manager + +What we are going to write is most likely less preferable to the following +alternatives: +- using Nix home-manager +- committing your `.gitconfig` into your + +In the next tutorial, we will use [home-manager][wtf-home-mgr] to replace the +functionality that we wrote. + +So why bother completing this? + +### Let's begin + +Welcome to the first tutorial in the [Let's Learn Nix][wtf-lln] series. Today we +are going to create a Nix derivation for one of your dotfiles. + +"Dotfiles" refers to a user's collection of configuration files. Typically these +files look like: +- `.vimrc` +- `.xsessionrc` +- `.bashrc` + +The leading "dot" at the beginning gives dotfiles their name. + +You probably have amassed a collection of dotfiles whether or not you are +aware. For example, if you use [git][wtf-git], the file `~/.gitconfig` should +exist on your machine. You can verify this with: + +```shell +$ stat ~/.gitconfig +``` + +When I was first learning `git`, I learned to configure it using commands I +found in books and tutorials that often looked like: + +```shell +$ git config user.email +``` + +The `~/.gitconfig` file on your machine may look something like this: + +```.gitconfig +[user] + name = John Cleese + email = john@flying-circus.com + username = jcleese +[core] + editor = emacs +[web] + browser = google-chrome +[rerere] + enabled = 1 + autoupdate = 1 +[push] + default = matching +[color] + ui = auto +[alias] + a = add --all + ai = add -i + b = branch + cl = clone + cp = cherry-pick + d = diff + fo = fetch origin + lg = log --oneline --graph --decorate + ps = push + pb = pull --rebase + s = status +``` + +As I ran increasingly more `git config` commands to configure my `git` +preferences, the size of my `.gitconfig` increased, and the less likely I was to +remember which options I set to which values. + +Thankfully a coworker at the time, Ryan ([@rschmukler][who-ryan]), told me that +he version-controlled his `.gitconfig` file along with his other configuration +files (e.g. `.vimrc`) in a repository he called "dotfiles". + +Version-controlling your dotfiles improves upon a workflow where you have a +variety of configuration files scattered around your machine. + +If you look at the above `.gitconfig`, can you spot the dependencies? + +We explicitly depend `emacs` and `google-chrome`. We also *implicitly* depend on +`git`: there is not much value of having a `.gitconfig` file if you also do not +have `git` installed on your machine. + +Dependencies: +- `emacs` +- `google-chrome` + +Let's use Nix to generate this `.gitconfig` file. Here is what I would like our +API to be: + +Let's create a file `gitconfig.nix` and build our function section-by-section: + +TODO(wpcarro): Link to sections here +- options.user +- options.core +- options.web +- options.rerere +- options.push +- options.color +- options.alias + +```shell +$ touch gitconfig.nix +``` + +### options.user + +```haskell +AttrSet -> String +``` + +```nix +user = { + name = "John Cleese"; + email = "john@flying-circus.com"; + username = "jcleese"; +}; +``` + +```.gitconfig +[user] + name = John Cleese + email = john@flying-circus.com + username = jcleese +``` + +### options.core + +```nix +core = { + editor = "${pkgs.emacs}/bin/emacs"; +}; +``` + +```.gitconfig +[core] + editor = /nix/store/<hash>-emacs-<version>/bin/emacs +``` + +### options.web + +```nix +web.browser = "${pkgs.google-chrome}/bin/google-chrome"; +``` + +```.gitconfig +[web] + browser = /nix/store/<hash>-google-chrome-<version>/bin/google-chrome +``` + +### options.rerere + +```nix +rerere = { + enabled = true; + autoupdate = true; +}; +``` + +```.gitconfig +[rerere] + enabled = 1 + autoupdate = 1 +``` + +### options.push + +```nix +push.default = "matching"; +``` + +```.gitconfig +[push] + default = matching +``` + +### options.color + +```nix +color.ui = "auto"; +``` + +```.gitconfig +[color] + ui = auto +``` + +We need to define a function named `gitconfig` that creates a Nix [derivation][wtf-derivation]: + +```nix +# file: gitconfig.nix +let + # Import the <nixpkgs> package repository. + pkgs = import <nixpkgs> {}; + + # Stringify the attribute set, `xs`, as a multilined string formatted as "<key> = <value>". + # See attrsets.nix for more functions that work with attribute sets. + encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs); + + # Define out function name `gitconfig` that accepts an `options` argument. + gitconfig = options: pkgs.stdenv.mkDerivation { + # The gitconfig file that Nix builds will be located /nix/store/some-hash-gitconfig. + name = "gitconfig"; + src = pkgs.writeTextFile ".gitconfig" '' + [user] + name = ${options.user.name} + email = ${options.user.email} + username = ${options.user.username} + [core] + editor = ${options.core.editor} + [web] + editor = ${options.web.browser} + [rerere] + enabled = ${if options.rerere.enabled "1" else "0"} + autoupdate = ${if options.rerere.autoupdate "1" else "0"} + [push] + default = ${options.push.default} + [color] + ui = ${options.color.ui} + [alias] + ${encodeAttrSet options.aliases} + ''; + buildPhase = '' + ${pkgs.coreutils}/bin/cp $src $out + ''; + installPhase = '' + ${pkgs.coreutils}/bin/ln -s $out ~/.gitconfig + ''; + }; +} in gitconfig { + user = { + name = "John Cleese"; + email = "john@flying-circus.com"; + username = "jcleese"; + }; + core = { + editor = "${pkgs.emacs}/bin/emacs"; + }; + web.browser = "${pkgs.google-chrome}/bin/google-chrome"; + rerere = { + enabled = true; + autoupdate = true; + }; + push.default = "matching"; + color.ui = "auto"; + aliases = { + a = "add --all"; + ai = "add -i"; + b = "branch"; + cl = "clone"; + cp = "cherry-pick"; + d = "diff"; + fo = "fetch origin"; + lg = "log --oneline --graph --decorate"; + ps = "push"; + pb = "pull --rebase"; + s = "status"; + }; +} +``` + +### options.alias + +We want to write a function that accepts an attribute set and returns a +string. While Nix is a dynamically typed programming language, thinking in types +helps me clarify what I'm trying to write. + +```haskell +encodeAttrSet :: AttrSet -> String +``` + +I prefer using a Haskell-inspired syntax for describing type signatures. Even if +you haven't written Haskell before, you may find the syntax intuitive. + +Here is a non comprehensive, but demonstrative list of example type signatures: +- `[String]`: A list of strings (i.e. `[ "cogito" "ergo" "sum" ]`) +- `AttrSet`: A nix attribute set (i.e. `{ name = "John Cleese"; age = 80; }`). +- `add :: Integer -> Integer -> Integer`: A function named `add` that accepts + two integers and returns an integer. + +Specifically, we want to make sure that when we call: + +```nix +encodeAttrSet { + a = "add --all"; + b = "branch"; +} +``` + +...it returns a string that looks like this: + +```.gitconfig +a = "add --all" +b = "branch" +``` + + +TODO(wpcarro): @tazjin's nix-1p mentions this. Link to it. +Nix has useful functions scattered all over the place: +- `lib.nix` +- `list.nix` +- `lib.attrSet` + +But I cannot recall exactly which functions we will need to write +`encodeAttrSet`. In these cases, I do the following: +1. Run `nix repl`. +2. Browse the Nix source code. + +Google "nix attribute sets" and find the Github link to `attrsets.nix`. + +You should consider repeating this search but instead of searching for +"attribute sets" search for "lists" and "strings". That is how I found the +functions needed to write `encodeAttrSet`. Let's return to our `nix repl`. + +Load the nixpkgs set: + +```nix +nix-repl> :l <nixpkgs> +Added 11484 variables. +``` + +Define a test input called `attrs`: + +```nix +nix-repl> attrs = { fname = "John"; lname = "Cleese"; } +``` + +Map the attribute set into `[String]` using `lib.mapAttrsToList`: + +```nix +nix-repl> lib.mapAttrsToList (k: v: "${k} = ${toString v}") attrs +[ "fname = John" "lname = Cleese" ] +``` + +Now join the `[String]` together using `lib.concatStringsSep`: + +```nix +nix-repl> lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") attrs) +"fname = John\nlname = Cleese" +``` + +Now let's use this to define our function `encodeAttrSet`: + +```nix +# file: gitconfig.nix +encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs); +``` + +### Using nixpkgs search + +[Nixpkgs search][wtf-nixpkgs-search]. + +### Conclusion + +We learned how to help ourselves. + +- Where does `emacs` exist? What about `google-chrome`? [nixpkgs search][wtf-nixpkgs-search] +- Verify that I have it? [nix REPL][using-nix-repl] + +We used Nix to create our first derivation. + +[wtf-lln]: /lets-learn-nix +[wtf-git]: https://git-scm.com/ +[wtf-derivation]: https://nixos.org/nixos/nix-pills/our-first-derivation.html +[wtf-nixpkgs-search]: https://nixos.org/nixos/packages.html?channel=nixos-19.09 +[using-nix-repl]: /using-the-nix-repl +[wtf-home-mgr]: https://github.com/rycee/home-manager +[who-ryan]: https://twitter.com/rschmukler diff --git a/users/wpcarro/website/blog/content/english/lets-learn-nix-tutorial-reproducibility.md b/users/wpcarro/website/blog/content/english/lets-learn-nix-tutorial-reproducibility.md new file mode 100644 index 000000000000..c80892164dbe --- /dev/null +++ b/users/wpcarro/website/blog/content/english/lets-learn-nix-tutorial-reproducibility.md @@ -0,0 +1,41 @@ +--- +title: "Lets Learn Nix: Tutorial Reproducibility" +date: 2020-03-17T18:34:58Z +draft: true +--- + +## Install Nix + +Link to nixos page. + +## The rest + +Start with this... + +```shell +$ mkdir ~/lets-learn-nix +$ cd ~/lets-learn-nix +``` + +...done. Copy the following and paste it into a file name `shell.nix`. + +```nix +# file: shell.nix +let + pkgs = import (builtins.fetchGit { + url = "https://github.com/NixOS/nixpkgs-channels"; + ref = "refs/heads/nixos-19.09"; + }) {} +in pkgs.mkShell { + buildInputs = with pkgs; [ + git + ]; + NIX_PATH = "nixpkgs=${pkgs}"; +}; +``` + +...then... + +```shell +$ nix-shell +``` diff --git a/users/wpcarro/website/blog/content/english/lets-learn-nix.md b/users/wpcarro/website/blog/content/english/lets-learn-nix.md new file mode 100644 index 000000000000..a7c9a22e422e --- /dev/null +++ b/users/wpcarro/website/blog/content/english/lets-learn-nix.md @@ -0,0 +1,58 @@ +--- +title: "Lets Learn Nix" +date: 2020-03-13T21:50:47Z +draft: false +--- + +## Background + +[Nix][wtf-nix] may be the most useful tool that I use. I consider it as valuable +as [Git][wtf-git] or [Emacs][wtf-emacs]. My friend, David ([@dmjio][who-dmjio]), +first introduced me to Nix when we worked together at a Haskell startup in +NYC. Before this, I had been managing my system configuration using software +that I wrote -- first in Bash, then in Python, then in Golang. + +It took me awhile to understand Nix. I left the NYC startup, joined Google, and +relocated to London. Here I met another Nix-enlightened monk, Vincent +([@tazjin][who-tazjin]), who patiently taught me enough Nix to become +self-reliant and productive. + +Many resources exist to learn Nix; the Nix community on IRC continues to help me +and others effectively use Nix. I'm creating this series to write the tutorials +that I would have found useful when I started learning Nix. If you are just +beginning your Nix journey, I hope these tutorials help you. + +## Goals + +I aim to make each tutorial in the "Let's Learn Nix" series: +- Actionable: Readers will be writing code. +- Digestible: Readers should be able to finish each tutorial in fifteen minutes. +- Reproducible: Readers should expect the output of their code to match what + these tutorials claim they should see. + +## About the author + +My name is William ([@wpcarro][who-wpcarro]). My three favorite tools are Git, +Emacs, and Nix. I am an American expat currently working at Google in +London. While during the day I primarily write Java, Python, and TypeScript, I +prefer functional programming. I use Nix to deploy software and manage the +multiple machines across which I work. + +## Let's Begin + +Before we get started, Nix is a programming language. To familiarize yourself +with the syntax, semantics, and idioms, consider reading this brief [Nix One +Pager][nix-1p]. I recommend keeping it around as a reference. + +When I was first learning Nix, I wanted to use it to manage my dotfiles. Our +first tutorial will help you get started: [Let's Learn Nix: +Dotfiles][lln-dotfiles] + +[wtf-nix]: https://nixos.org +[wtf-git]: https://git-scm.com +[wtf-emacs]: https://www.gnu.org/software/emacs +[who-dmjio]: https://twitter.com/dmjio +[who-tazjin]: https://twitter.com/tazjin +[who-wpcarro]: https://twitter.com/wpcarro +[lln-dotfiles]: /lets-learn-nix-dotfiles +[nix-1p]: https://github.com/tazjin/nix-1p diff --git a/users/wpcarro/website/blog/content/english/nix-and-hugo.md b/users/wpcarro/website/blog/content/english/nix-and-hugo.md new file mode 100644 index 000000000000..ff0fe70205da --- /dev/null +++ b/users/wpcarro/website/blog/content/english/nix-and-hugo.md @@ -0,0 +1,5 @@ +--- +title: "Deploy Hugo blog with Nix" +date: 2020-03-11T18:42:32Z +draft: true +--- diff --git a/users/wpcarro/website/blog/content/english/self-hosting.md b/users/wpcarro/website/blog/content/english/self-hosting.md new file mode 100644 index 000000000000..1705541d820c --- /dev/null +++ b/users/wpcarro/website/blog/content/english/self-hosting.md @@ -0,0 +1,6 @@ +--- +title: "Self Hosting" +date: 2020-03-11T22:53:56Z +draft: true +--- + diff --git a/users/wpcarro/website/blog/default.nix b/users/wpcarro/website/blog/default.nix new file mode 100644 index 000000000000..cea9e8706cd6 --- /dev/null +++ b/users/wpcarro/website/blog/default.nix @@ -0,0 +1,12 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "blog.wpcarro.dev"; + buildInputs = with pkgs; [ hugo ]; + src = builtins.path { path = ./.; name = "blog"; }; + buildPhase = '' + mkdir -p $out + ${pkgs.hugo}/bin/hugo --minify --destination $out + ''; + dontInstall = true; +} diff --git a/users/wpcarro/website/blog/shell.nix b/users/wpcarro/website/blog/shell.nix new file mode 100644 index 000000000000..7ca3a9713ebf --- /dev/null +++ b/users/wpcarro/website/blog/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + hugo + ]; +} diff --git a/users/wpcarro/website/blog/themes/tailwind/.gitignore b/users/wpcarro/website/blog/themes/tailwind/.gitignore new file mode 100644 index 000000000000..eb1a2c123ccc --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/.gitignore @@ -0,0 +1,5 @@ +.vscode/ + +node_modules +public/build +yarn.lock diff --git a/users/wpcarro/website/blog/themes/tailwind/archetypes/default.md b/users/wpcarro/website/blog/themes/tailwind/archetypes/default.md new file mode 100644 index 000000000000..f7e5e03d9ebd --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/archetypes/default.md @@ -0,0 +1,7 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +description: "" +date: {{ .Date }} +draft: true +tags: [] +--- diff --git a/users/wpcarro/website/blog/themes/tailwind/i18n/en.yaml b/users/wpcarro/website/blog/themes/tailwind/i18n/en.yaml new file mode 100644 index 000000000000..8326be14acf4 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/i18n/en.yaml @@ -0,0 +1,5 @@ +- id: back_home + translation: "Back Home" + +- id: not_found_page_title + translation: "Whoops! The page you're looking for doesn't exist :(" diff --git a/users/wpcarro/website/blog/themes/tailwind/images/screenshot.png b/users/wpcarro/website/blog/themes/tailwind/images/screenshot.png new file mode 100644 index 000000000000..3ca0b46b2d2b --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/images/screenshot.png Binary files differdiff --git a/users/wpcarro/website/blog/themes/tailwind/images/tn.png b/users/wpcarro/website/blog/themes/tailwind/images/tn.png new file mode 100644 index 000000000000..785504cd0176 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/images/tn.png Binary files differdiff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/404.html b/users/wpcarro/website/blog/themes/tailwind/layouts/404.html new file mode 100644 index 000000000000..afa69fec743e --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/404.html @@ -0,0 +1,12 @@ +{{ define "heading"}} +<div> + <a class="text-lg mb-8 inline-block" href="{{ .Site.BaseURL | relLangURL }}">← {{ i18n "back_home" }}</a> + <h1 class="text-4xl font-bold">{{ i18n "not_found_page_title" }}</h1> +</div> +{{ end }} + +{{ define "content" }} +<section class="mb-24"> + <img src="{{ "images/404-background.png" | relURL }}" alt="Page Not Found"> +</section> +{{ end }} diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/_default/baseof.html b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/baseof.html new file mode 100644 index 000000000000..2cc783dae038 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/baseof.html @@ -0,0 +1,87 @@ +<!doctype html> +<html lang="{{ .Site.Params.LanguageCode }}"> + +<head> + <meta charset="utf-8"> + {{ hugo.Generator }} + + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + + <!-- Twitter Card --> + <meta name="twitter:card" content="summary"> + <meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}"> + <meta name="twitter:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}"> + <meta name="twitter:site" content="{{ .Site.BaseURL }}"> + <meta name="twitter:creator" content="{{ .Params.Author }}"> + <meta name="twitter:image" content="{{ .Site.Params.Avatar | absURL }}"> + + <!-- Open-Graph Data --> + <meta property="og:locale" content="{{ .Site.Params.LanguageCode }}"> + <meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}"> + <meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}"> + <meta property="og:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}"> + <meta property="og:url" content="{{ .Permalink }}"> + <meta property="og:site_name" content="{{ .Site.Title }}"> + <meta property="og:image" content="{{ .Site.Params.Avatar | absURL }}"> + + <title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}</title> + + <meta name="author" content="{{ .Site.Params.Author }}"> + <meta name="description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}"> + + <!-- RSS --> + {{ with .OutputFormats.Get "RSS" }} + <link rel="alternate" href="{{ .RelPermalink | absURL }}" type="application/rss+xml" title="{{ $.Site.Title }}"> + {{ end }} + + <!-- Translations --> + {{ if .IsTranslated }} + {{ range .Translations }} + <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" title="{{ .Site.Title }}"> + {{ end }} + {{ end }} + + <!-- Stylesheets --> + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro|Arvo:400,700"> + <link rel="stylesheet" href="{{ "css/theme.css" | absURL }}"> + <link rel="stylesheet" href="{{ "css/chroma.dracula.css" | absURL }}"> +</head> +<body class="font-serif border-t-4 border-blue-500 antialiased"> + <div class="w-full p-6 md:w-2/3 md:px-0 md:mx-auto xl:w-2/5"> + <header class="mb-6"> + <!-- All the pages must have a heading block, defaults to a link for the home page and a title. --> + <div class="mb-6 md:flex md:items-center"> + {{ block "heading" . }} + <div> + {{ partial "back-home.html" . }} + <h1 class="text-4xl font-bold">{{ .Title }}</h1> + </div> + {{ end }} + </div> + + <!-- If the blog has translation, they shoul be displayed here. --> + {{ if .IsTranslated }} + <nav> + {{ range $i, $lang := .Translations }} + {{ if $i }}/{{ end }} + <a href="{{ .Permalink }}">{{ $lang.Language.LanguageName }}</a> + {{ end}} + </nav> + {{ end }} + </header> + + <!-- The content block. --> + {{ block "content" . }}{{ end }} + + <footer> + <p> + © {{ now.Format "2006"}}. Thank you for reading. + </p> + </footer> + </div> + + {{ template "_internal/google_analytics.html" . }} + <script data-ad-client="ca-pub-6018268443649487" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> +</body> +</html> diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/_default/list.html b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/list.html new file mode 100644 index 000000000000..d781ce752a4a --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/list.html @@ -0,0 +1,7 @@ +{{ define "content" }} +<section class="mb-24"> + {{ range site.RegularPages.GroupByDate "2006" -}} + {{ partial "posts.html" . }} + {{ end }} +</section> +{{ end }} diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/_default/single.html b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/single.html new file mode 100644 index 000000000000..e4485f324e9c --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/_default/single.html @@ -0,0 +1,28 @@ +{{ define "heading" }} +<div> + {{ partial "back-home.html" . }} + + <!-- Title and Publication Date --> + <h1 class="text-4xl font-bold">{{ .Title }}</h1> + <time datetime="{{ .Date.Format "2006-01-02 15:04:05 MST" }}">{{ .Date.Format "02 Jan 2006" }}</time> + + <!-- Tags --> + {{ with .Params.tags }} + <ol class="mt-4"> + {{ range . }} + <li class="inline-block"> + <a class="border-none text-gray-800 text-xs bg-gray-400 hover:bg-gray-600 hover:text-white rounded-sm px-3 py-1" href="{{ "tags" | absURL }}/{{ . | urlize }}">{{ . }}</a> + </li> + {{ end }} + </ol> + {{ end }} +</div> +{{ end }} + +{{ define "content" }} +<article class="mb-12"> + {{ .Content }} + + {{ template "_internal/disqus.html" . }} +</article> +{{ end }} diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/index.html b/users/wpcarro/website/blog/themes/tailwind/layouts/index.html new file mode 100644 index 000000000000..4869c466b613 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/index.html @@ -0,0 +1,18 @@ +{{ define "heading" }} +{{ if .Site.Params.Avatar }} +<img class="hidden md:block w-20 rounded-full mr-6" src="{{ .Site.Params.Avatar | absURL }}" alt="{{ .Site.Params.Author }}"> +{{ end }} + +<div> + <h1 class="text-4xl font-bold">{{ .Site.Title }}</h1> + <p>{{ .Site.Params.tagline }}</p> +</div> +{{ end }} + +{{ define "content" }} +<section class="mb-24"> + {{ range site.RegularPages.GroupByDate "2006" -}} + {{ partial "posts.html" . }} + {{ end }} +</section> +{{ end }} diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/partials/back-home.html b/users/wpcarro/website/blog/themes/tailwind/layouts/partials/back-home.html new file mode 100644 index 000000000000..4064d5256137 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/partials/back-home.html @@ -0,0 +1 @@ +<a class="text-lg mb-8 inline-block" href="{{ .Site.BaseURL | relLangURL }}">← {{ i18n "back_home" }}</a> diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/partials/posts.html b/users/wpcarro/website/blog/themes/tailwind/layouts/partials/posts.html new file mode 100644 index 000000000000..0ebd4ca7e9b4 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/partials/posts.html @@ -0,0 +1,12 @@ +<div> + <h2 class="text-3xl font-bold mb-2">{{ .Key }}</h2> + + <ol> + {{ range .Pages -}} + <li class="mb-6 md:flex md:flex-row"> + <time class=" block md:flex-l-24" datetime="{{ .Date.Format "2006-01-02 15:04:05 MST" }}">{{ .Date.Format "Jan 02"}}</time> + <a class="text-lg md:ml-12" href="{{ .RelPermalink }}">{{ .Title }}</a> + </li> + {{- end }} + </ol> +</div> diff --git a/users/wpcarro/website/blog/themes/tailwind/layouts/taxonomy/terms.html b/users/wpcarro/website/blog/themes/tailwind/layouts/taxonomy/terms.html new file mode 100644 index 000000000000..76da49ed0a09 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/layouts/taxonomy/terms.html @@ -0,0 +1,13 @@ +{{ define "content" }} +<section class="mb-24"> + <ol class="-mx-2"> + {{ range .Pages -}} + <li class="inline-block mx-2 my-2"> + <a class="border-none text-gray-800 bg-gray-400 hover:bg-gray-600 hover:text-white rounded-sm px-3 py-1" href="{{ .RelPermalink }}"> + {{ .Title }} + </a> + </li> + {{- end }} + </ol> +</section> +{{ end }} diff --git a/users/wpcarro/website/blog/themes/tailwind/license.md b/users/wpcarro/website/blog/themes/tailwind/license.md new file mode 100644 index 000000000000..196326bb0e7e --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/license.md @@ -0,0 +1,7 @@ +Copyright 2019 Ian Rodrigues. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/users/wpcarro/website/blog/themes/tailwind/package.json b/users/wpcarro/website/blog/themes/tailwind/package.json new file mode 100644 index 000000000000..86bb50f9490a --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/package.json @@ -0,0 +1,17 @@ +{ + "scripts": { + "watch": "cross-env NODE_ENV=development postcss scss/theme.scss -o static/css/theme.css --watch", + "build": "cross-env NODE_ENV=production postcss scss/theme.scss -o static/css/theme.css" + }, + "dependencies": { + "autoprefixer": "^9.5.1", + "tailwindcss": "^1.0" + }, + "devDependencies": { + "@fullhuman/postcss-purgecss": "^1.2.0", + "concurrently": "^4.1.0", + "cross-env": "^5.2.0", + "cssnano": "^4.1.10", + "postcss-cli": "^6.1.2" + } +} diff --git a/users/wpcarro/website/blog/themes/tailwind/postcss.config.js b/users/wpcarro/website/blog/themes/tailwind/postcss.config.js new file mode 100644 index 000000000000..6efcb774fa7c --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/postcss.config.js @@ -0,0 +1,14 @@ +const purgecss = require('@fullhuman/postcss-purgecss')({ + content: ['./layouts/**/*.html'], + defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [] +}) + +module.exports = { + plugins: [ + require('tailwindcss'), + require('autoprefixer'), + ...process.env.NODE_ENV === 'production' + ? [purgecss, require('cssnano')] + : [] + ] +} diff --git a/users/wpcarro/website/blog/themes/tailwind/readme.md b/users/wpcarro/website/blog/themes/tailwind/readme.md new file mode 100644 index 000000000000..cdb69a619730 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/readme.md @@ -0,0 +1,62 @@ +# TailwindCSS Journal + +_TailwindCSS Journal_ is a minimalist theme for [Hugo](https://gohugo.io) using [TailwindCSS](https://tailwindcss.com). + +Based on [Journal](https://dashdashzako.github.io/hugo-journal-demo/), it also focuses on improving reading experience with no fancy effect. + +It uses [Chroma](https://gohugo.io/content-management/syntax-highlighting/) for the syntaxic coloration of code snippets. + +Demo is available [here](https://ianrodrigues.github.io/hugo-tailwind-journal-demo/). + +## Installation + +Please refer to the [Hugo documentation](https://gohugo.io/themes/installing/). + +## Configuration + +A few parameters should be adjusted in the site config: + +```toml +baseURL = "https://username.github.io/" +disqusShortname = "username" +googleAnalytics = "UA-XXXXXXXXX-X" +title = "Tailwind Journal" +theme = "hugo-tailwind-journal" +pygmentsCodeFences = true +pygmentsUseClasses = true + +[taxonomies] + tag = "tags" + +[permalinks] + posts = "/posts/:year/:month/:title/" + +[params] + author = "John Doe" + avatar = "images/avatar.jpg" + description = "A minimalist journal template for Hugo using TailwindCSS." + tagline = "A minimalist journal template for Hugo using TailwindCSS." + +[languages] + [languages.en] + contentDir = "content/english" + languageCode = "en-us" + languageName = "๐บ๐ธ English" + weight = 1 + + [languages.pt-br] + contentDir = "content/portuguese" + description = "Um template minimalista para Hugo usando TailwindCSS." + languageCode = "pt-br" + languageName = "๐ง๐ท Portuguรชs" + tagline = "Um template minimalista para Hugo usando TailwindCSS." + weight = 2 + + [languages.de] + contentDir = "content/german" + description = "Eine minimalistische Journalvorlage fรผr Hugo mit TailwindCSS." + languageCode = "de" + languageName = "๐ฉ๐ช Deutsch" + tagline = "Eine minimalistische Journalvorlage fรผr Hugo mit TailwindCSS." + weight = 3 +``` diff --git a/users/wpcarro/website/blog/themes/tailwind/scss/theme.scss b/users/wpcarro/website/blog/themes/tailwind/scss/theme.scss new file mode 100644 index 000000000000..9da9261b63a1 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/scss/theme.scss @@ -0,0 +1,51 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply text-gray-800; +} + +h1, h2, h3, h4, h5, h6, strong { + @apply text-gray-900; +} + +article > p, ul, ol { + @apply text-lg tracking-wide; +} + +article > div, p, ul, ol, pre:not(:last-child) { + @apply mb-6; +} + +article > ol, ul { + @apply list-disc ml-8; +} + +article > li:not(:last-of-type) { + @apply mb-2; +} + +article p > code { + @apply p-1/2 bg-gray-400; +} + +article > h2 { + @apply text-2xl my-8 font-bold text-black; +} + +a { + @apply border-b border-black text-black; +} + +/* purgecss ignore */ +pre.chroma { + @apply p-4 overflow-x-auto font-mono text-lg; +} + +@screen md { + /* purgecss ignore */ + div.highlight { + @apply -mx-12; + } +} diff --git a/users/wpcarro/website/blog/themes/tailwind/static/css/chroma.dracula.css b/users/wpcarro/website/blog/themes/tailwind/static/css/chroma.dracula.css new file mode 100644 index 000000000000..5e903f4fd7bc --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/static/css/chroma.dracula.css @@ -0,0 +1 @@ +.chroma{color:#f8f8f2;background-color:#282a36}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffc}.chroma .lnt{margin-right:.4em;padding:0 .4em 0 .4em;color:#7f7f7f}.chroma .ln{margin-right:.4em;padding:0 .4em 0 .4em;color:#7f7f7f}.chroma .k{color:#ff79c6}.chroma .kc{color:#ff79c6}.chroma .kd{color:#8be9fd;font-style:italic}.chroma .kn{color:#ff79c6}.chroma .kp{color:#ff79c6}.chroma .kr{color:#ff79c6}.chroma .kt{color:#8be9fd}.chroma .na{color:#50fa7b}.chroma .nb{color:#8be9fd;font-style:italic}.chroma .nc{color:#50fa7b}.chroma .nf{color:#50fa7b}.chroma .nl{color:#8be9fd;font-style:italic}.chroma .nt{color:#ff79c6}.chroma .nv{color:#8be9fd;font-style:italic}.chroma .vc{color:#8be9fd;font-style:italic}.chroma .vg{color:#8be9fd;font-style:italic}.chroma .vi{color:#8be9fd;font-style:italic}.chroma .s{color:#f1fa8c}.chroma .sa{color:#f1fa8c}.chroma .sb{color:#f1fa8c}.chroma .sc{color:#f1fa8c}.chroma .dl{color:#f1fa8c}.chroma .sd{color:#f1fa8c}.chroma .s2{color:#f1fa8c}.chroma .se{color:#f1fa8c}.chroma .sh{color:#f1fa8c}.chroma .si{color:#f1fa8c}.chroma .sx{color:#f1fa8c}.chroma .sr{color:#f1fa8c}.chroma .s1{color:#f1fa8c}.chroma .ss{color:#f1fa8c}.chroma .m{color:#bd93f9}.chroma .mb{color:#bd93f9}.chroma .mf{color:#bd93f9}.chroma .mh{color:#bd93f9}.chroma .mi{color:#bd93f9}.chroma .il{color:#bd93f9}.chroma .mo{color:#bd93f9}.chroma .o{color:#ff79c6}.chroma .ow{color:#ff79c6}.chroma .c{color:#6272a4}.chroma .ch{color:#6272a4}.chroma .cm{color:#6272a4}.chroma .c1{color:#6272a4}.chroma .cs{color:#6272a4}.chroma .cp{color:#ff79c6}.chroma .cpf{color:#ff79c6}.chroma .gd{color:#8b080b}.chroma .ge{text-decoration:underline}.chroma .gh{font-weight:700}.chroma .gi{font-weight:700}.chroma .go{color:#44475a}.chroma .gu{font-weight:700}.chroma .gl{text-decoration:underline} diff --git a/users/wpcarro/website/blog/themes/tailwind/static/css/theme.css b/users/wpcarro/website/blog/themes/tailwind/static/css/theme.css new file mode 100644 index 000000000000..4d9cc992fd36 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/static/css/theme.css @@ -0,0 +1 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}a{background-color:transparent}strong{font-weight:bolder}img{border-style:none}button,input{font-family:inherit;font-size:100%;line-height:1.15;margin:0;overflow:visible}button{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}legend{color:inherit;display:table;max-width:100%;white-space:normal}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}html{box-sizing:border-box;font-family:sans-serif}*,:after,:before{box-sizing:inherit}h1,h2,p{margin:0}button{background:transparent;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}ol{list-style:none;margin:0;padding:0}html{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{border:0 solid #e2e8f0}img{border-style:solid}input::-webkit-input-placeholder{color:#a0aec0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}h1,h2{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input{padding:0;line-height:inherit;color:inherit}canvas,img{display:block;vertical-align:middle}img{max-width:100%;height:auto}.bg-gray-200{background-color:#edf2f7}.bg-gray-400{background-color:#cbd5e0}.hover\:bg-gray-600:hover{background-color:#718096}.border-blue-500{border-color:#4299e1}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.border-none{border-style:none}.border-t-4{border-top-width:4px}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.font-serif{font-family:Arvo}.font-bold{font-weight:700}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mb-12{margin-bottom:3rem}.mb-24{margin-bottom:6rem}.p-6{padding:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.text-gray-800{color:#2d3748}.hover\:text-white:hover{color:#fff}.text-xs{font-size:.75rem}.text-lg{font-size:1.125rem}.text-3xl{font-size:1.875rem}.text-4xl{font-size:2.25rem}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-20{width:5rem}.w-full{width:100%}body{color:#2d3748}h1,h2,strong{color:#1a202c}article>p,ol{font-size:1.125rem;letter-spacing:.025em}article>div,ol,p{margin-bottom:1.5rem}article>ol{list-style-type:disc;margin-left:2rem}article>li:not(:last-of-type){margin-bottom:.5rem}article>h2{font-size:1.5rem;margin-top:2rem;margin-bottom:2rem;font-weight:700;color:#000}a{border-bottom-width:1px;border-color:#000;color:#000}pre.chroma{padding:1rem;overflow-x:auto;font-family:Source Code Pro;font-size:1.125rem}@media (min-width:768px){div.highlight{margin-left:-3rem;margin-right:-3rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex{display:-webkit-box;display:flex}.md\:flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}.md\:items-center{-webkit-box-align:center;align-items:center}.md\:flex-l-24{-webkit-box-flex:0;flex:0 0 6rem}.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-12{margin-left:3rem}.md\:px-0{padding-left:0;padding-right:0}.md\:w-2\/3{width:66.666667%}}@media (min-width:1280px){.xl\:w-2\/5{width:40%}} \ No newline at end of file diff --git a/users/wpcarro/website/blog/themes/tailwind/static/images/404-background.png b/users/wpcarro/website/blog/themes/tailwind/static/images/404-background.png new file mode 100644 index 000000000000..4b945386a001 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/static/images/404-background.png Binary files differdiff --git a/users/wpcarro/website/blog/themes/tailwind/tailwind.config.js b/users/wpcarro/website/blog/themes/tailwind/tailwind.config.js new file mode 100644 index 000000000000..f3714704df46 --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/tailwind.config.js @@ -0,0 +1,18 @@ +module.exports = { + theme: { + extend: { + flex: { + 'l-24': '0 0 6rem' + }, + fontFamily: { + serif: ['Arvo'], + mono: ['Source Code Pro'] + }, + padding: { + '1/2': '0.125rem' + } + } + }, + variants: {}, + plugins: [], +} diff --git a/users/wpcarro/website/blog/themes/tailwind/theme.toml b/users/wpcarro/website/blog/themes/tailwind/theme.toml new file mode 100644 index 000000000000..bfdefa7160df --- /dev/null +++ b/users/wpcarro/website/blog/themes/tailwind/theme.toml @@ -0,0 +1,12 @@ +name = "Tailwind Journal" +license = "MIT" +licenselink = "https://github.com/ianrodrigues/hugo-tailwind-journal/blob/master/license.md" +description = "A minimalist journal template for Hugo using TailwindCSS." +homepage = "https://github.com/ianrodrigues/hugo-tailwind-journal" +tags = ["minimalist", "reading", "blog", "tailwindcss"] +features = ["blog"] +min_version = "0.54.0" + +[author] + name = "Ian Rodrigues" + homepage = "https://ianrodrigues.dev" diff --git a/users/wpcarro/website/default.nix b/users/wpcarro/website/default.nix new file mode 100644 index 000000000000..bd4bd7f85228 --- /dev/null +++ b/users/wpcarro/website/default.nix @@ -0,0 +1,13 @@ +{ pkgs, briefcase, ... }: + +pkgs.stdenv.mkDerivation { + name = "wpcarro.dev"; + src = builtins.path { path = ./.; name = "website"; }; + installPhase = '' + mkdir -p $out + cp $src/index.html $out + + mkdir -p $out/habits + cp -r ${briefcase.website.habit-screens} $out/habits/index.html + ''; +} diff --git a/users/wpcarro/website/goals/.envrc b/users/wpcarro/website/goals/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/website/goals/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/website/goals/.gitignore b/users/wpcarro/website/goals/.gitignore new file mode 100644 index 000000000000..a86b565e2238 --- /dev/null +++ b/users/wpcarro/website/goals/.gitignore @@ -0,0 +1,3 @@ +/.cache +/dist/**/* +/node_modules \ No newline at end of file diff --git a/users/wpcarro/website/goals/README.md b/users/wpcarro/website/goals/README.md new file mode 100644 index 000000000000..a397d8d32f84 --- /dev/null +++ b/users/wpcarro/website/goals/README.md @@ -0,0 +1,5 @@ +# Goals + +Kent C. Dodds taught me that I can create a React website without any bundling +software. To practice this, I created a simple React app to track some of my +goals. Notice how I wrote JSX inside of a `<script type="text/babel">` tag. diff --git a/users/wpcarro/website/goals/default.nix.ignore b/users/wpcarro/website/goals/default.nix.ignore new file mode 100644 index 000000000000..2006204bec94 --- /dev/null +++ b/users/wpcarro/website/goals/default.nix.ignore @@ -0,0 +1,19 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "goals-webpage"; + src = builtins.path { path = ./.; name = "goals"; }; + buildInputs = with pkgs; [ + nodejs + # Exposes lscpu for parcel.js + utillinux + ]; + # parcel.js needs number of CPUs + PARCEL_WORKERS = "1"; + buildPhase = '' + npx parcel build src/index.html --public-url ./ + ''; + installPhase = '' + mv dist $out + ''; +} diff --git a/users/wpcarro/website/goals/package.json b/users/wpcarro/website/goals/package.json new file mode 100644 index 000000000000..c812e9a38ecd --- /dev/null +++ b/users/wpcarro/website/goals/package.json @@ -0,0 +1,28 @@ +{ + "name": "tailwindcss", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "parcel src/index.html & npx tsc --watch --noEmit", + "prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\"" + }, + "devDependencies": { + "@types/node": "^13.9.3", + "parcel-bundler": "^1.12.4", + "prettier": "^2.0.2", + "tailwindcss": "^1.2.0", + "typescript": "^3.8.3" + }, + "dependencies": { + "@reduxjs/toolkit": "^1.2.5", + "@types/react": "^16.9.25", + "@types/react-dom": "^16.9.5", + "@types/react-redux": "^7.1.7", + "@types/react-router-dom": "^5.1.3", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.1.2" + } +} diff --git a/users/wpcarro/website/goals/postcss.config.js b/users/wpcarro/website/goals/postcss.config.js new file mode 100644 index 000000000000..a23795075b11 --- /dev/null +++ b/users/wpcarro/website/goals/postcss.config.js @@ -0,0 +1,5 @@ +const tailwindcss = require("tailwindcss"); + +module.exports = { + plugins: [tailwindcss("./tailwind.config.js")], +}; diff --git a/users/wpcarro/website/goals/shell.nix b/users/wpcarro/website/goals/shell.nix new file mode 100644 index 000000000000..083254beefd0 --- /dev/null +++ b/users/wpcarro/website/goals/shell.nix @@ -0,0 +1,9 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + yarn + ]; +} diff --git a/users/wpcarro/website/goals/src/App.tsx b/users/wpcarro/website/goals/src/App.tsx new file mode 100644 index 000000000000..53195d9af98f --- /dev/null +++ b/users/wpcarro/website/goals/src/App.tsx @@ -0,0 +1,132 @@ +import React from "react"; + +function ProgressBar(props: { + done: number; + total: number; + units: string; + color: string; +}) { + const { done, total, units, color } = props; + const width = Math.floor((done / total) * 100); + const rest = 100 - width; + + let [fg, bg] = [`bg-${color}-600`, `bg-${color}-100`]; + + if (color === "white") { + [fg, bg] = ["bg-gray-600", "bg-gray-100"]; + } + + return ( + <div className={`relative ${bg} h-5`}> + <div + className={`${fg} h-5 absolute top-0 left-0`} + style={{ width: `${width}%` }} + ></div> + <p className="absolute text-xs pl-1 pt-1"> + {done} of {total} {units} + </p> + </div> + ); +} + +function Goal(props: { + subject: string; + goal: string; + done: number; + total: number; + units: string; + color: string; +}) { + const { subject, goal, done, total, units, color } = props; + const width = "6em"; + + const Tr = (props: { + label: string; + value: string; + valueComponent?: React.ReactElement; + }) => ( + <tr className="flex py-2"> + <td className="text-gray-600" style={{ width: width }}> + {props.label} + </td> + <td className="flex-1"> + {props.valueComponent ? props.valueComponent : props.value} + </td> + </tr> + ); + + return ( + <table className="w-full mb-10"> + <tbody> + <Tr label="Subject" value={subject} /> + <Tr label="Goal" value={goal} /> + <Tr + label="Progress" + value={goal} + valueComponent={ + <ProgressBar + done={done} + total={total} + units={units} + color={color} + /> + } + /> + </tbody> + </table> + ); +} + +function Copy(props: { children: React.ReactNode }) { + return <p className="pb-4 leading-loose">{props.children}</p>; +} + +function App() { + return ( + <div className="container mx-auto font-mono"> + <section> + <h1 className="text-center pt-12 pb-6">Goals</h1> + <Copy> + For me, a goal is something that is difficult for me to complete but + easy for me to measure. I tend to add new goals as time progresses, + mistakenly assuming that I can support additional goals for free. To + counterbalance my tendancy to casually accumulate goals, I aim to only + have three goals; I will not add a new goal until I complete an + existing goal. I created and published this page to clarify that idea. + </Copy> + <Copy> + Here are my current goals and the progress I have made towards + achieving them. + </Copy> + </section> + <section className="pt-4"> + <Goal + subject="Meditation" + goal="Meditate for 10,000 hours" + done={100} + total={10000} + units="hrs" + color="purple" + /> + <Goal + subject="Debt" + goal="Pay my student debt balance" + done={30000} + total={70000} + units="USD" + color="green" + /> + <Goal + subject="Brazilian Jiu Jitsu" + goal="Train until an instructor gives me a black belt" + done={1} + total={5} + units="belts" + color="white" + /> + </section> + </div> + ); +} + +export default App; diff --git a/users/wpcarro/website/goals/src/index.css b/users/wpcarro/website/goals/src/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/website/goals/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/website/goals/src/index.html b/users/wpcarro/website/goals/src/index.html new file mode 100644 index 000000000000..91752af916a4 --- /dev/null +++ b/users/wpcarro/website/goals/src/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="./index.css" /> + </head> + <body> + <div id="mount"></div> + <script src="./index.tsx"></script> + </body> +</html> diff --git a/users/wpcarro/website/goals/src/index.tsx b/users/wpcarro/website/goals/src/index.tsx new file mode 100644 index 000000000000..51fc2363ec71 --- /dev/null +++ b/users/wpcarro/website/goals/src/index.tsx @@ -0,0 +1,5 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; + +ReactDOM.render(<App />, document.getElementById("mount")); diff --git a/users/wpcarro/website/goals/tailwind.config.js b/users/wpcarro/website/goals/tailwind.config.js new file mode 100644 index 000000000000..3da6fa0dc7b7 --- /dev/null +++ b/users/wpcarro/website/goals/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + theme: { + extend: {}, + }, + variants: {}, + plugins: [], +}; diff --git a/users/wpcarro/website/goals/tsconfig.json b/users/wpcarro/website/goals/tsconfig.json new file mode 100644 index 000000000000..fe07ec1da4d4 --- /dev/null +++ b/users/wpcarro/website/goals/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src/**/*"] +} diff --git a/users/wpcarro/website/goals/yarn.lock b/users/wpcarro/website/goals/yarn.lock new file mode 100644 index 000000000000..358922838b16 --- /dev/null +++ b/users/wpcarro/website/goals/yarn.lock @@ -0,0 +1,5670 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== + dependencies: + browserslist "^4.9.1" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.4", "@babel/generator@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" + integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== + dependencies: + "@babel/types" "^7.9.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-builder-react-jsx-experimental@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" + integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-module-imports" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-builder-react-jsx@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" + integrity sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-compilation-targets@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" + integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== + dependencies: + "@babel/compat-data" "^7.8.6" + browserslist "^4.9.1" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== + dependencies: + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" + integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.4.4", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.3.tgz#043a5fc2ad8b7ea9facddc4e802a1f0f25da7255" + integrity sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A== + +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" + integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.8" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-flow@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz#f2c883bd61a6316f2c89380ae5122f923ba4527f" + integrity sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" + integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" + integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" + integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-split-export-declaration" "^7.8.3" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" + integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-flow-strip-types@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" + integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-flow" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-modules-amd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== + dependencies: + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.8.7": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" + integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.1.tgz#d03af29396a6dc51bfa24eefd8005a9fd381152a" + integrity sha512-+xIZ6fPoix7h57CNO/ZeYADchg1tFyX9NDsnmNFFua8e1JNPln156mzS+8AQe1On2X2GLlANHJWHIXbMCqWDkQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.9.0" + "@babel/helper-builder-react-jsx-experimental" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" + integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" + integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.0" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.0" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.4.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" + integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" + integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@iarna/toml@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" + integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@parcel/fs@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-1.11.0.tgz#fb8a2be038c454ad46a50dc0554c1805f13535cd" + integrity sha512-86RyEqULbbVoeo8OLcv+LQ1Vq2PKBAvWTU9fCgALxuCTbbs5Ppcvll4Vr+Ko1AnmMzja/k++SzNAwJfeQXVlpA== + dependencies: + "@parcel/utils" "^1.11.0" + mkdirp "^0.5.1" + rimraf "^2.6.2" + +"@parcel/logger@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" + integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== + dependencies: + "@parcel/workers" "^1.11.0" + chalk "^2.1.0" + grapheme-breaker "^0.3.2" + ora "^2.1.0" + strip-ansi "^4.0.0" + +"@parcel/utils@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" + integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== + +"@parcel/watcher@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" + integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== + dependencies: + "@parcel/utils" "^1.11.0" + chokidar "^2.1.5" + +"@parcel/workers@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-1.11.0.tgz#7b8dcf992806f4ad2b6cecf629839c41c2336c59" + integrity sha512-USSjRAAQYsZFlv43FUPdD+jEGML5/8oLF0rUzPQTtK4q9kvaXr49F5ZplyLz5lox78cLZ0TxN2bIDQ1xhOkulQ== + dependencies: + "@parcel/utils" "^1.11.0" + physical-cpu-count "^2.0.0" + +"@reduxjs/toolkit@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.2.5.tgz#149aa62da12a18a67a30495cb63fd897003f2272" + integrity sha512-/OWoW5mniUXAomw4+3ZhhWodcs1/SRvK2HKyxLXdW6vKgmJhiBiSHe/huHARlKWujEmGaJrkafx548GE494bCQ== + dependencies: + immer "^4.0.1" + redux "^4.0.0" + redux-devtools-extension "^2.13.8" + redux-immutable-state-invariant "^2.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@^13.9.3": + version "13.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" + integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" + integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + +"@types/react-dom@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7" + integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a" + integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" + integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*", "@types/react@^16.9.25": + version "16.9.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e" + integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn-walk@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + +acorn@^6.0.1, acorn@^6.0.4: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +ansi-to-html@^0.6.4: + version "0.6.14" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" + integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== + dependencies: + entities "^1.1.2" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.4.5: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + dependencies: + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.26" + postcss-value-parser "^4.0.2" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-runtime@^6.11.6, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-types@^6.15.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon-walk@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babylon-walk/-/babylon-walk-1.0.2.tgz#3b15a5ddbb482a78b4ce9c01c8ba181702d9d6ce" + integrity sha1-OxWl3btIKni0zpwByLoYFwLZ1s4= + dependencies: + babel-runtime "^6.11.6" + babel-types "^6.15.0" + lodash.clone "^4.5.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brfs@^1.2.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" + integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== + dependencies: + quote-stream "^1.0.1" + resolve "^1.1.5" + static-module "^2.2.0" + through2 "^2.0.0" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.8.3, browserslist@^4.9.1: + version "4.11.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.0.tgz#aef4357b10a8abda00f97aac7cd587b2082ba1ad" + integrity sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.380" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035: + version "1.0.30001036" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" + integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" + integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" + integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== + +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@~1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + dependencies: + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^2.4.0, core-js@^2.6.5: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.4: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-modules-loader-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" + integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= + dependencies: + icss-replace-symbols "1.1.0" + postcss "6.0.1" + postcss-modules-extract-imports "1.1.0" + postcss-modules-local-by-default "1.2.0" + postcss-modules-scope "1.1.0" + postcss-modules-values "1.3.0" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-selector-tokenizer@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" + integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + regexpu-core "^4.6.0" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= + +css-what@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" + integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.0.0, cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" + integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + dependencies: + css-tree "1.0.0-alpha.37" + +cssom@0.3.x, cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +csstype@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +deasync@^0.1.14: + version "0.1.19" + resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.19.tgz#e7ea89fcc9ad483367e8a48fe78f508ca86286e8" + integrity sha512-oh3MRktfnPlLysCPpBpKZZzb4cUC/p0aA3SyRGp15lN30juJBTo/CiD0d4fR+f1kBtUQoJj1NE9RPNWQ7BQ9Mg== + dependencies: + bindings "^1.5.0" + node-addon-api "^1.7.1" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.380: + version "1.3.381" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.381.tgz#952678ff91a5f36175a3832358a6dd2de3bf62b7" + integrity sha512-JQBpVUr83l+QOqPQpj2SbOve1bBE4ACpmwcMNqWlZmfib7jccxJ02qFNichDpZ5LS4Zsqc985NIPKegBIZjK8Q== + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +entities@^1.1.1, entities@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +envinfo@^7.3.1: + version "7.5.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" + integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.11.0, escodegen@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +falafel@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" + integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + dependencies: + acorn "^7.1.1" + foreach "^2.0.5" + isarray "^2.0.1" + object-keys "^1.0.6" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-glob@^2.2.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +filesize@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +grapheme-breaker@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz#5b9e6b78c3832452d2ba2bb1cb830f96276410ac" + integrity sha1-W55reMODJFLSuiuxy4MPlidkEKw= + dependencies: + brfs "^1.2.0" + unicode-trie "^0.3.1" + +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-tags@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" + integrity sha1-x43mW1Zjqll5id0rerSSANfk25g= + +htmlnano@^0.2.2: + version "0.2.5" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-0.2.5.tgz#134fd9548c7cbe51c8508ce434a3f9488cff1b0b" + integrity sha512-X1iPSwXG/iF9bVs+/obt2n6F64uH0ETkA8zp7qFDmLW9/+A6ueHGeb/+qD67T21qUY22owZPMdawljN50ajkqA== + dependencies: + cssnano "^4.1.10" + normalize-html-whitespace "^1.0.0" + posthtml "^0.12.0" + posthtml-render "^1.1.5" + purgecss "^1.4.0" + svgo "^1.3.2" + terser "^4.3.9" + uncss "^0.17.2" + +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +immer@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-4.0.2.tgz#9ff0fcdf88e06f92618a5978ceecb5884e633559" + integrity sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-html@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-html/-/is-html-1.1.0.tgz#e04f1c18d39485111396f9a0273eab51af218464" + integrity sha1-4E8cGNOUhRETlvmgJz6rUa8hhGQ= + dependencies: + html-tags "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isarray@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" + integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== + dependencies: + vlq "^0.2.2" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + dependencies: + source-map "^0.5.6" + +merge2@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-libs-browser@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" + +normalize-html-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" + integrity sha512-9ui7CGtOOlehQu0t/OhhlmDyc71mKVlv+4vF+me4iZLPrNtRL2xoquEdfZxasC/bdQi/Hr3iTrpyRKIG+ocabA== + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-inspect@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" + integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-2.1.0.tgz#6caf2830eb924941861ec53a173799e008b51e5b" + integrity sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA== + dependencies: + chalk "^2.3.1" + cli-cursor "^2.1.0" + cli-spinners "^1.1.0" + log-symbols "^2.2.0" + strip-ansi "^4.0.0" + wcwidth "^1.0.1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parcel-bundler@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" + integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.4.4" + "@babel/generator" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/plugin-transform-flow-strip-types" "^7.4.4" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/preset-env" "^7.4.4" + "@babel/runtime" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + "@iarna/toml" "^2.2.0" + "@parcel/fs" "^1.11.0" + "@parcel/logger" "^1.11.1" + "@parcel/utils" "^1.11.0" + "@parcel/watcher" "^1.12.1" + "@parcel/workers" "^1.11.0" + ansi-to-html "^0.6.4" + babylon-walk "^1.0.2" + browserslist "^4.1.0" + chalk "^2.1.0" + clone "^2.1.1" + command-exists "^1.2.6" + commander "^2.11.0" + core-js "^2.6.5" + cross-spawn "^6.0.4" + css-modules-loader-core "^1.1.0" + cssnano "^4.0.0" + deasync "^0.1.14" + dotenv "^5.0.0" + dotenv-expand "^5.1.0" + envinfo "^7.3.1" + fast-glob "^2.2.2" + filesize "^3.6.0" + get-port "^3.2.0" + htmlnano "^0.2.2" + is-glob "^4.0.0" + is-url "^1.2.2" + js-yaml "^3.10.0" + json5 "^1.0.1" + micromatch "^3.0.4" + mkdirp "^0.5.1" + node-forge "^0.7.1" + node-libs-browser "^2.0.0" + opn "^5.1.0" + postcss "^7.0.11" + postcss-value-parser "^3.3.1" + posthtml "^0.11.2" + posthtml-parser "^0.4.0" + posthtml-render "^1.1.3" + resolve "^1.4.0" + semver "^5.4.1" + serialize-to-js "^3.0.0" + serve-static "^1.12.4" + source-map "0.6.1" + terser "^3.7.3" + v8-compile-cache "^2.0.0" + ws "^5.1.1" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +physical-cpu-count@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" + integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-calc@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" + integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-nested@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248" + integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw== + dependencies: + postcss "^7.0.21" + postcss-selector-parser "^6.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-selector-parser@6.0.2, postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1, postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27: + version "7.0.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" + integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +posthtml-parser@^0.4.0, posthtml-parser@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" + integrity sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg== + dependencies: + htmlparser2 "^3.9.2" + +posthtml-render@^1.1.3, posthtml-render@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.2.0.tgz#3df0c800a8bbb95af583a94748520469477addf4" + integrity sha512-dQB+hoAKDtnI94RZm/wxBUH9My8OJcXd0uhWmGh2c7tVtQ85A+OS3yCN3LNbFtPz3bViwBJXAeoi+CBGMXM0DA== + +posthtml@^0.11.2: + version "0.11.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" + integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +posthtml@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.12.0.tgz#6e2a2fcd774eaed1a419a95c5cc3a92b676a40a6" + integrity sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" + integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +purgecss@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.4.2.tgz#67ab50cb4f5c163fcefde56002467c974e577f41" + integrity sha512-hkOreFTgiyMHMmC2BxzdIw5DuC6kxAbP/gGOGd3MEsF3+5m69rIvUEPaxrnoUtfODTFKe9hcXjGwC6jcjoyhOw== + dependencies: + glob "^7.1.3" + postcss "^7.0.14" + postcss-selector-parser "^6.0.0" + yargs "^14.0.0" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +quote-stream@^1.0.1, quote-stream@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" + integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + dependencies: + buffer-equal "0.0.1" + minimist "^1.1.3" + through2 "^2.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-dom@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-redux@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +redux-devtools-extension@^2.13.8: + version "2.13.8" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" + integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== + +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpu-core@^4.6.0, regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.5, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.4.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-to-js@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" + integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== + +serve-static@^1.12.4: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-copy@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +static-eval@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.5.tgz#f0782e66999c4b3651cda99d9ce59c507d188f71" + integrity sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA== + dependencies: + escodegen "^1.11.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +static-module@^2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" + integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== + dependencies: + concat-stream "~1.6.0" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "~1.9.0" + falafel "^2.1.0" + has "^1.0.1" + magic-string "^0.22.4" + merge-source-map "1.0.4" + object-inspect "~1.4.0" + quote-stream "~1.0.2" + readable-stream "~2.3.3" + shallow-copy "~0.0.1" + static-eval "^2.0.0" + through2 "~2.0.3" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0, svgo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291" + integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ== + dependencies: + autoprefixer "^9.4.5" + bytes "^3.0.0" + chalk "^3.0.0" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +terser@^3.7.3: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + +terser@^4.3.9: + version "4.6.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" + integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0, through2@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +uncss@^0.17.2: + version "0.17.3" + resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.3.tgz#50fc1eb4ed573ffff763458d801cd86e4d69ea11" + integrity sha512-ksdDWl81YWvF/X14fOSw4iu8tESDHFIeyKIeDrK6GEVTQvqJc1WlOEXqostNwOCi3qAj++4EaLsdAgPmUbEyog== + dependencies: + commander "^2.20.0" + glob "^7.1.4" + is-absolute-url "^3.0.1" + is-html "^1.1.0" + jsdom "^14.1.0" + lodash "^4.17.15" + postcss "^7.0.17" + postcss-selector-parser "6.0.2" + request "^2.88.0" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unicode-trie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" + integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^5.1.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3" + integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^14.0.0: + version "14.2.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" + integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== + dependencies: + cliui "^5.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^15.0.1" diff --git a/users/wpcarro/website/habit-screens/.envrc b/users/wpcarro/website/habit-screens/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/website/habit-screens/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/website/habit-screens/.gitignore b/users/wpcarro/website/habit-screens/.gitignore new file mode 100644 index 000000000000..0c1c258f65a4 --- /dev/null +++ b/users/wpcarro/website/habit-screens/.gitignore @@ -0,0 +1,2 @@ +/elm-stuff +/Main.min.js diff --git a/users/wpcarro/website/habit-screens/README.md b/users/wpcarro/website/habit-screens/README.md new file mode 100644 index 000000000000..506cdf9c4ac8 --- /dev/null +++ b/users/wpcarro/website/habit-screens/README.md @@ -0,0 +1,31 @@ +# Habit Screens + +Problem: I would like to increase the rate at which I complete my daily, weekly, +monthly, yearly habits. + +Solution: Habit Screens are mounted in strategic locations throughout my +apartment. Each Habit Screen displays the habits that I should complete that +day, and I can tap each item to mark it as complete. I will encounter the Habit +Screens in my bedroom, kitchen, and bathroom, so I will have adequate "cues" to +focus my attention. By marking each item as complete and tracking the results +over time, I will have more incentive to maintain my consistency +(i.e. "reward"). + +## Elm + +Elm has one of the best developer experiences that I'm aware of. The error +messages are helpful and the entire experience is optimized to improve the ease +of writing web applications. + +### Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ npx tailwindcss build index.css -o output.css +$ elm-live -- src/Main.elm --output=Main.min.js +``` + +You can now view your web client at `http://localhost:8000`! diff --git a/users/wpcarro/website/habit-screens/default.nix b/users/wpcarro/website/habit-screens/default.nix new file mode 100644 index 000000000000..c042dc8c8021 --- /dev/null +++ b/users/wpcarro/website/habit-screens/default.nix @@ -0,0 +1,61 @@ +{ pkgs ? <nixpkgs> , ... }: + +with pkgs; + +let + mkDerivation = + { srcs ? ./elm-srcs.nix + , src + , name + , srcdir ? "./src" + , targets ? [] + , registryDat ? ./registry.dat + , outputJavaScript ? false + }: + stdenv.mkDerivation { + inherit name src; + + buildInputs = [ elmPackages.elm ] + ++ lib.optional outputJavaScript nodePackages_10_x.uglify-js; + + buildPhase = pkgs.elmPackages.fetchElmDeps { + elmPackages = import srcs; + elmVersion = "0.19.1"; + inherit registryDat; + }; + + installPhase = let + elmfile = module: "${srcdir}/${builtins.replaceStrings ["."] ["/"] module}.elm"; + extension = if outputJavaScript then "js" else "html"; + in '' + mkdir -p $out/share/doc + ${lib.concatStrings (map (module: '' + echo "compiling ${elmfile module}" + elm make ${elmfile module} --output $out/${module}.${extension} --docs $out/share/doc/${module}.json + ${lib.optionalString outputJavaScript '' + echo "minifying ${elmfile module}" + uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \ + | uglifyjs --mangle --output=$out/${module}.min.${extension} + ''} + '') targets)} + ''; + }; + mainDotElm = mkDerivation { + name = "elm-app-0.1.0"; + srcs = ./elm-srcs.nix; + src = ./.; + targets = ["Main"]; + srcdir = "./src"; + outputJavaScript = true; + }; +in stdenv.mkDerivation { + name = "habit-screens"; + buildInputs = []; + src = builtins.path { path = ./.; name = "habit-screens"; }; + buildPhase = '' + mkdir -p $out + cp index.html output.css ${mainDotElm}/Main.min.js $out + ''; + dontInstall = true; +} + diff --git a/users/wpcarro/website/habit-screens/design.md b/users/wpcarro/website/habit-screens/design.md new file mode 100644 index 000000000000..f16361ac4358 --- /dev/null +++ b/users/wpcarro/website/habit-screens/design.md @@ -0,0 +1,43 @@ +# Habit Screens + +## MVP + +One Android tablet mounted on my bedroom wall displaying habits for that day. I +can toggle the done/todo states on each item by tapping it. There is no +server. All of the habits are defined in the client-side codebase. The +application is available online at wpcarro.dev. + +## Ideal + +Three Android tablets: one mounted in my bedroom, another in my bathroom, and a +third in my kitchen. Each tablet has a view of the current state of the +application and updates in soft real-time. + +I track the rates at which I complete each habit and compile all of the metrics +into a dashboard. When I move a habit from Saturday to Sunday or from Wednesday +to Monday, it doesn't break the tracking. + +When I complete a habit, it quickly renders some consistency information like +"completing rate since Monday" and "length of current streak". + +I don't consider this application that sensitive, but for security purposes I +would like this application to be accessible within a private network. This is +something I don't know too much about setting up, but I don't want anyone to be +able to visit www.BillAndHisHabits.com and change the states of my habits and +affect the tracking data. Nor do I want anyone to be able to make HTTP requests +to my server to alter the state of the application without my permission. + +## Client + +Language: Elm + +### Updates across devices + +Instead of setting up sockets on my server and subscribing to them from the +client, I think each device should poll the server once every second (or fewer) +to maintain UI consistency. + +## Server + +Language: Haskell +Database: SQLite diff --git a/users/wpcarro/website/habit-screens/elm-srcs.nix b/users/wpcarro/website/habit-screens/elm-srcs.nix new file mode 100644 index 000000000000..167708e072b0 --- /dev/null +++ b/users/wpcarro/website/habit-screens/elm-srcs.nix @@ -0,0 +1,77 @@ +{ + + "elm-community/maybe-extra" = { + sha256 = "0qslmgswa625d218djd3p62pnqcrz38f5p558mbjl6kc1ss0kzv3"; + version = "5.2.0"; + }; + + "elm/html" = { + sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; + version = "1.0.0"; + }; + + "elm-community/random-extra" = { + sha256 = "1dg2nz77w2cvp16xazbdsxkkw0xc9ycqpkd032faqdyky6gmz9g6"; + version = "3.1.0"; + }; + + "elm/svg" = { + sha256 = "1cwcj73p61q45wqwgqvrvz3aypjyy3fw732xyxdyj6s256hwkn0k"; + version = "1.0.1"; + }; + + "justinmimbs/date" = { + sha256 = "1f0wcl8yhlvp3x4rj53rdy4r4ga7lkl6n8fdfh6b96scz2rnxmd4"; + version = "3.2.1"; + }; + + "elm/browser" = { + sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13"; + version = "1.0.2"; + }; + + "elm/core" = { + sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; + version = "1.0.5"; + }; + + "elm-community/list-extra" = { + sha256 = "1ayv3148drynqnxdfwpjxal8vwzgsjqanjg7yxp6lhdcbkxgd3vd"; + version = "8.2.3"; + }; + + "elm/random" = { + sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl"; + version = "1.0.0"; + }; + + "elm/time" = { + sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; + version = "1.0.0"; + }; + + "elm/json" = { + sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh"; + version = "1.1.3"; + }; + + "elm/parser" = { + sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512"; + version = "1.1.0"; + }; + + "owanturist/elm-union-find" = { + sha256 = "13gm7msnp0gr1lqia5m7m4lhy3m6kvjg37d304whb3psn88wqhj5"; + version = "1.0.0"; + }; + + "elm/url" = { + sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; + version = "1.0.0"; + }; + + "elm/virtual-dom" = { + sha256 = "0q1v5gi4g336bzz1lgwpn5b1639lrn63d8y6k6pimcyismp2i1yg"; + version = "1.0.2"; + }; +} diff --git a/users/wpcarro/website/habit-screens/elm.json b/users/wpcarro/website/habit-screens/elm.json new file mode 100644 index 000000000000..6839ac4fabdc --- /dev/null +++ b/users/wpcarro/website/habit-screens/elm.json @@ -0,0 +1,32 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0", + "justinmimbs/date": "3.2.1" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/parser": "1.1.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/website/habit-screens/index.css b/users/wpcarro/website/habit-screens/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/website/habit-screens/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/website/habit-screens/index.html b/users/wpcarro/website/habit-screens/index.html new file mode 100644 index 000000000000..b587e0901284 --- /dev/null +++ b/users/wpcarro/website/habit-screens/index.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Elm SPA</title> + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Chilanka"> + <link rel="stylesheet" href="./output.css"> + <style> + body { + font-family: 'Chilanka'; + } + </style> + <script src="./Main.min.js"></script> + </head> + <body> + <div id="mount"></div> + <script> + Elm.Main.init({node: document.getElementById("mount")}); + </script> + </body> +</html> diff --git a/users/wpcarro/website/habit-screens/output.css b/users/wpcarro/website/habit-screens/output.css new file mode 100644 index 000000000000..b522419aa3b7 --- /dev/null +++ b/users/wpcarro/website/habit-screens/output.css @@ -0,0 +1,103571 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} + +/** + * Manually forked from SUIT CSS Base: https://github.com/suitcss/base + * A thin layer on top of normalize.css that provides a starting point more + * suitable for web applications. + */ + +/** + * Removes the default spacing and border for appropriate elements. + */ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +button { + background-color: transparent; + background-image: none; +} + +/** + * Work around a Firefox/IE bug where the transparent `button` background + * results in a loss of the default `button` focus styles. + */ + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +fieldset { + margin: 0; + padding: 0; +} + +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +/** + * Tailwind custom reset styles + */ + +/** + * 1. Use the user's configured `sans` font-family (with Tailwind's default + * sans-serif font stack as a fallback) as a sane default. + * 2. Use Tailwind's default "normal" line-height so the user isn't forced + * to override it to ensure consistency even when using the default theme. + */ + +html { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */ + line-height: 1.5; /* 2 */ +} + +/** + * 1. Prevent padding and border from affecting element width. + * + * We used to set this in the html element and inherit from + * the parent element for everything else. This caused issues + * in shadow-dom-enhanced elements like <details> where the content + * is wrapped by a div with box-sizing set to `content-box`. + * + * https://github.com/mozdevs/cssremedy/issues/4 + * + * + * 2. Allow adding a border to an element by just adding a border-width. + * + * By default, the way the browser specifies that an element should have no + * border is by setting it's border-style to `none` in the user-agent + * stylesheet. + * + * In order to easily add borders to elements by just setting the `border-width` + * property, we change the default border-style for all elements to `solid`, and + * use border-width to hide them instead. This way our `border` utilities only + * need to set the `border-width` property instead of the entire `border` + * shorthand, making our border utilities much more straightforward to compose. + * + * https://github.com/tailwindcss/tailwindcss/pull/116 + */ + +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: #e2e8f0; /* 2 */ +} + +/* + * Ensure horizontal rules are visible by default + */ + +hr { + border-top-width: 1px; +} + +/** + * Undo the `border-style: none` reset that Normalize applies to images so that + * our `border-{width}` utilities have the expected effect. + * + * The Normalize reset is unnecessary for us since we default the border-width + * to 0 on all elements. + * + * https://github.com/tailwindcss/tailwindcss/issues/362 + */ + +img { + border-style: solid; +} + +textarea { + resize: vertical; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #a0aec0; +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + color: #a0aec0; +} + +input::placeholder, +textarea::placeholder { + color: #a0aec0; +} + +button, +[role="button"] { + cursor: pointer; +} + +table { + border-collapse: collapse; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/** + * Reset links to optimize for opt-in styling instead of + * opt-out. + */ + +a { + color: inherit; + text-decoration: inherit; +} + +/** + * Reset form element properties that are easy to forget to + * style explicitly so you don't inadvertently introduce + * styles that deviate from your design system. These styles + * supplement a partial reset that is already applied by + * normalize.css. + */ + +button, +input, +optgroup, +select, +textarea { + padding: 0; + line-height: inherit; + color: inherit; +} + +/** + * Use the configured 'mono' font family for elements that + * are expected to be rendered with a monospace font, falling + * back to the system monospace stack if there is no configured + * 'mono' font family. + */ + +pre, +code, +kbd, +samp { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +/** + * Make replaced elements `display: block` by default as that's + * the behavior you want almost all of the time. Inspired by + * CSS Remedy, with `svg` added as well. + * + * https://github.com/mozdevs/cssremedy/issues/14 + */ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + vertical-align: middle; +} + +/** + * Constrain images and videos to the parent width and preserve + * their instrinsic aspect ratio. + * + * https://github.com/mozdevs/cssremedy/issues/14 + */ + +img, +video { + max-width: 100%; + height: auto; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +.space-y-0 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0px * var(--space-y-reverse)); +} + +.space-x-0 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0px * var(--space-x-reverse)); + margin-left: calc(0px * calc(1 - var(--space-x-reverse))); +} + +.space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.25rem * var(--space-y-reverse)); +} + +.space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.25rem * var(--space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.5rem * var(--space-y-reverse)); +} + +.space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.5rem * var(--space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.75rem * var(--space-y-reverse)); +} + +.space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.75rem * var(--space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1rem * var(--space-y-reverse)); +} + +.space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1rem * var(--space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.25rem * var(--space-y-reverse)); +} + +.space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.25rem * var(--space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.5rem * var(--space-y-reverse)); +} + +.space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.5rem * var(--space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2rem * var(--space-y-reverse)); +} + +.space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2rem * var(--space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2.5rem * var(--space-y-reverse)); +} + +.space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2.5rem * var(--space-x-reverse)); + margin-left: calc(2.5rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(3rem * var(--space-y-reverse)); +} + +.space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(3rem * var(--space-x-reverse)); + margin-left: calc(3rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(4rem * var(--space-y-reverse)); +} + +.space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(4rem * var(--space-x-reverse)); + margin-left: calc(4rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(5rem * var(--space-y-reverse)); +} + +.space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(5rem * var(--space-x-reverse)); + margin-left: calc(5rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(6rem * var(--space-y-reverse)); +} + +.space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(6rem * var(--space-x-reverse)); + margin-left: calc(6rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(8rem * var(--space-y-reverse)); +} + +.space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(8rem * var(--space-x-reverse)); + margin-left: calc(8rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(10rem * var(--space-y-reverse)); +} + +.space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(10rem * var(--space-x-reverse)); + margin-left: calc(10rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(12rem * var(--space-y-reverse)); +} + +.space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(12rem * var(--space-x-reverse)); + margin-left: calc(12rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(14rem * var(--space-y-reverse)); +} + +.space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(14rem * var(--space-x-reverse)); + margin-left: calc(14rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(16rem * var(--space-y-reverse)); +} + +.space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(16rem * var(--space-x-reverse)); + margin-left: calc(16rem * calc(1 - var(--space-x-reverse))); +} + +.space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1px * var(--space-y-reverse)); +} + +.space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1px * var(--space-x-reverse)); + margin-left: calc(1px * calc(1 - var(--space-x-reverse))); +} + +.-space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.25rem * var(--space-y-reverse)); +} + +.-space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.25rem * var(--space-x-reverse)); + margin-left: calc(-0.25rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.5rem * var(--space-y-reverse)); +} + +.-space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.5rem * var(--space-x-reverse)); + margin-left: calc(-0.5rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.75rem * var(--space-y-reverse)); +} + +.-space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.75rem * var(--space-x-reverse)); + margin-left: calc(-0.75rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1rem * var(--space-y-reverse)); +} + +.-space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1rem * var(--space-x-reverse)); + margin-left: calc(-1rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.25rem * var(--space-y-reverse)); +} + +.-space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.25rem * var(--space-x-reverse)); + margin-left: calc(-1.25rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.5rem * var(--space-y-reverse)); +} + +.-space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.5rem * var(--space-x-reverse)); + margin-left: calc(-1.5rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2rem * var(--space-y-reverse)); +} + +.-space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2rem * var(--space-x-reverse)); + margin-left: calc(-2rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2.5rem * var(--space-y-reverse)); +} + +.-space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2.5rem * var(--space-x-reverse)); + margin-left: calc(-2.5rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-3rem * var(--space-y-reverse)); +} + +.-space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-3rem * var(--space-x-reverse)); + margin-left: calc(-3rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-4rem * var(--space-y-reverse)); +} + +.-space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-4rem * var(--space-x-reverse)); + margin-left: calc(-4rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-5rem * var(--space-y-reverse)); +} + +.-space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-5rem * var(--space-x-reverse)); + margin-left: calc(-5rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-6rem * var(--space-y-reverse)); +} + +.-space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-6rem * var(--space-x-reverse)); + margin-left: calc(-6rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-8rem * var(--space-y-reverse)); +} + +.-space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-8rem * var(--space-x-reverse)); + margin-left: calc(-8rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-10rem * var(--space-y-reverse)); +} + +.-space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-10rem * var(--space-x-reverse)); + margin-left: calc(-10rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-12rem * var(--space-y-reverse)); +} + +.-space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-12rem * var(--space-x-reverse)); + margin-left: calc(-12rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-14rem * var(--space-y-reverse)); +} + +.-space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-14rem * var(--space-x-reverse)); + margin-left: calc(-14rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-16rem * var(--space-y-reverse)); +} + +.-space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-16rem * var(--space-x-reverse)); + margin-left: calc(-16rem * calc(1 - var(--space-x-reverse))); +} + +.-space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1px * var(--space-y-reverse)); +} + +.-space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1px * var(--space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--space-x-reverse))); +} + +.space-y-reverse > :not(template) ~ :not(template) { + --space-y-reverse: 1; +} + +.space-x-reverse > :not(template) ~ :not(template) { + --space-x-reverse: 1; +} + +.divide-y-0 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(0px * var(--divide-y-reverse)); +} + +.divide-x-0 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(0px * var(--divide-x-reverse)); + border-left-width: calc(0px * calc(1 - var(--divide-x-reverse))); +} + +.divide-y-2 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(2px * var(--divide-y-reverse)); +} + +.divide-x-2 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(2px * var(--divide-x-reverse)); + border-left-width: calc(2px * calc(1 - var(--divide-x-reverse))); +} + +.divide-y-4 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(4px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(4px * var(--divide-y-reverse)); +} + +.divide-x-4 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(4px * var(--divide-x-reverse)); + border-left-width: calc(4px * calc(1 - var(--divide-x-reverse))); +} + +.divide-y-8 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(8px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(8px * var(--divide-y-reverse)); +} + +.divide-x-8 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(8px * var(--divide-x-reverse)); + border-left-width: calc(8px * calc(1 - var(--divide-x-reverse))); +} + +.divide-y > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(1px * var(--divide-y-reverse)); +} + +.divide-x > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(1px * var(--divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--divide-x-reverse))); +} + +.divide-y-reverse > :not(template) ~ :not(template) { + --divide-y-reverse: 1; +} + +.divide-x-reverse > :not(template) ~ :not(template) { + --divide-x-reverse: 1; +} + +.divide-transparent > :not(template) ~ :not(template) { + border-color: transparent; +} + +.divide-current > :not(template) ~ :not(template) { + border-color: currentColor; +} + +.divide-black > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--divide-opacity)); +} + +.divide-white > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--divide-opacity)); +} + +.divide-gray-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--divide-opacity)); +} + +.divide-gray-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--divide-opacity)); +} + +.divide-gray-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--divide-opacity)); +} + +.divide-gray-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--divide-opacity)); +} + +.divide-gray-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--divide-opacity)); +} + +.divide-gray-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--divide-opacity)); +} + +.divide-gray-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--divide-opacity)); +} + +.divide-gray-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--divide-opacity)); +} + +.divide-gray-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--divide-opacity)); +} + +.divide-red-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--divide-opacity)); +} + +.divide-red-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--divide-opacity)); +} + +.divide-red-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--divide-opacity)); +} + +.divide-red-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--divide-opacity)); +} + +.divide-red-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--divide-opacity)); +} + +.divide-red-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--divide-opacity)); +} + +.divide-red-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--divide-opacity)); +} + +.divide-red-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--divide-opacity)); +} + +.divide-red-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--divide-opacity)); +} + +.divide-orange-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--divide-opacity)); +} + +.divide-orange-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--divide-opacity)); +} + +.divide-orange-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--divide-opacity)); +} + +.divide-orange-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--divide-opacity)); +} + +.divide-orange-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--divide-opacity)); +} + +.divide-orange-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--divide-opacity)); +} + +.divide-orange-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--divide-opacity)); +} + +.divide-orange-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--divide-opacity)); +} + +.divide-orange-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--divide-opacity)); +} + +.divide-yellow-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--divide-opacity)); +} + +.divide-yellow-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--divide-opacity)); +} + +.divide-yellow-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--divide-opacity)); +} + +.divide-yellow-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--divide-opacity)); +} + +.divide-yellow-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--divide-opacity)); +} + +.divide-yellow-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--divide-opacity)); +} + +.divide-yellow-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--divide-opacity)); +} + +.divide-yellow-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--divide-opacity)); +} + +.divide-yellow-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--divide-opacity)); +} + +.divide-green-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--divide-opacity)); +} + +.divide-green-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--divide-opacity)); +} + +.divide-green-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--divide-opacity)); +} + +.divide-green-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--divide-opacity)); +} + +.divide-green-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--divide-opacity)); +} + +.divide-green-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--divide-opacity)); +} + +.divide-green-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--divide-opacity)); +} + +.divide-green-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--divide-opacity)); +} + +.divide-green-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--divide-opacity)); +} + +.divide-teal-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--divide-opacity)); +} + +.divide-teal-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--divide-opacity)); +} + +.divide-teal-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--divide-opacity)); +} + +.divide-teal-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--divide-opacity)); +} + +.divide-teal-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--divide-opacity)); +} + +.divide-teal-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--divide-opacity)); +} + +.divide-teal-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--divide-opacity)); +} + +.divide-teal-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--divide-opacity)); +} + +.divide-teal-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--divide-opacity)); +} + +.divide-blue-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--divide-opacity)); +} + +.divide-blue-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--divide-opacity)); +} + +.divide-blue-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--divide-opacity)); +} + +.divide-blue-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--divide-opacity)); +} + +.divide-blue-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--divide-opacity)); +} + +.divide-blue-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--divide-opacity)); +} + +.divide-blue-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--divide-opacity)); +} + +.divide-blue-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--divide-opacity)); +} + +.divide-blue-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--divide-opacity)); +} + +.divide-indigo-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--divide-opacity)); +} + +.divide-indigo-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--divide-opacity)); +} + +.divide-indigo-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--divide-opacity)); +} + +.divide-indigo-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--divide-opacity)); +} + +.divide-indigo-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--divide-opacity)); +} + +.divide-indigo-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--divide-opacity)); +} + +.divide-indigo-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--divide-opacity)); +} + +.divide-indigo-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--divide-opacity)); +} + +.divide-indigo-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--divide-opacity)); +} + +.divide-purple-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--divide-opacity)); +} + +.divide-purple-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--divide-opacity)); +} + +.divide-purple-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--divide-opacity)); +} + +.divide-purple-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--divide-opacity)); +} + +.divide-purple-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--divide-opacity)); +} + +.divide-purple-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--divide-opacity)); +} + +.divide-purple-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--divide-opacity)); +} + +.divide-purple-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--divide-opacity)); +} + +.divide-purple-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--divide-opacity)); +} + +.divide-pink-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--divide-opacity)); +} + +.divide-pink-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--divide-opacity)); +} + +.divide-pink-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--divide-opacity)); +} + +.divide-pink-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--divide-opacity)); +} + +.divide-pink-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--divide-opacity)); +} + +.divide-pink-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--divide-opacity)); +} + +.divide-pink-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--divide-opacity)); +} + +.divide-pink-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--divide-opacity)); +} + +.divide-pink-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--divide-opacity)); +} + +.divide-solid > :not(template) ~ :not(template) { + border-style: solid; +} + +.divide-dashed > :not(template) ~ :not(template) { + border-style: dashed; +} + +.divide-dotted > :not(template) ~ :not(template) { + border-style: dotted; +} + +.divide-double > :not(template) ~ :not(template) { + border-style: double; +} + +.divide-none > :not(template) ~ :not(template) { + border-style: none; +} + +.divide-opacity-0 > :not(template) ~ :not(template) { + --divide-opacity: 0; +} + +.divide-opacity-25 > :not(template) ~ :not(template) { + --divide-opacity: 0.25; +} + +.divide-opacity-50 > :not(template) ~ :not(template) { + --divide-opacity: 0.5; +} + +.divide-opacity-75 > :not(template) ~ :not(template) { + --divide-opacity: 0.75; +} + +.divide-opacity-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.not-sr-only { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; +} + +.focus\:sr-only:focus { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.focus\:not-sr-only:focus { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.bg-fixed { + background-attachment: fixed; +} + +.bg-local { + background-attachment: local; +} + +.bg-scroll { + background-attachment: scroll; +} + +.bg-clip-border { + background-clip: border-box; +} + +.bg-clip-padding { + background-clip: padding-box; +} + +.bg-clip-content { + background-clip: content-box; +} + +.bg-clip-text { + -webkit-background-clip: text; + background-clip: text; +} + +.bg-transparent { + background-color: transparent; +} + +.bg-current { + background-color: currentColor; +} + +.bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); +} + +.bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); +} + +.bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); +} + +.bg-gray-200 { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); +} + +.bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); +} + +.bg-gray-400 { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); +} + +.bg-gray-500 { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); +} + +.bg-gray-600 { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); +} + +.bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); +} + +.bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); +} + +.bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); +} + +.bg-red-100 { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); +} + +.bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); +} + +.bg-red-300 { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); +} + +.bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); +} + +.bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); +} + +.bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); +} + +.bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); +} + +.bg-red-800 { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); +} + +.bg-red-900 { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); +} + +.bg-orange-100 { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); +} + +.bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); +} + +.bg-orange-300 { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); +} + +.bg-orange-400 { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); +} + +.bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); +} + +.bg-orange-600 { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); +} + +.bg-orange-700 { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); +} + +.bg-orange-800 { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); +} + +.bg-orange-900 { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); +} + +.bg-yellow-100 { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); +} + +.bg-yellow-200 { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); +} + +.bg-yellow-300 { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); +} + +.bg-yellow-400 { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); +} + +.bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); +} + +.bg-yellow-600 { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); +} + +.bg-yellow-700 { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); +} + +.bg-yellow-800 { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); +} + +.bg-yellow-900 { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); +} + +.bg-green-100 { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); +} + +.bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); +} + +.bg-green-300 { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); +} + +.bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); +} + +.bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); +} + +.bg-green-600 { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); +} + +.bg-green-700 { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); +} + +.bg-green-800 { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); +} + +.bg-green-900 { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); +} + +.bg-teal-100 { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); +} + +.bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); +} + +.bg-teal-300 { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); +} + +.bg-teal-400 { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); +} + +.bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); +} + +.bg-teal-600 { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); +} + +.bg-teal-700 { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); +} + +.bg-teal-800 { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); +} + +.bg-teal-900 { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); +} + +.bg-blue-100 { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); +} + +.bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); +} + +.bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); +} + +.bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); +} + +.bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); +} + +.bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); +} + +.bg-blue-700 { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); +} + +.bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); +} + +.bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); +} + +.bg-indigo-100 { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); +} + +.bg-indigo-200 { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); +} + +.bg-indigo-300 { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); +} + +.bg-indigo-400 { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); +} + +.bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); +} + +.bg-indigo-600 { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); +} + +.bg-indigo-700 { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); +} + +.bg-indigo-800 { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); +} + +.bg-indigo-900 { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); +} + +.bg-purple-100 { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); +} + +.bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); +} + +.bg-purple-300 { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); +} + +.bg-purple-400 { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); +} + +.bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); +} + +.bg-purple-600 { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); +} + +.bg-purple-700 { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); +} + +.bg-purple-800 { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); +} + +.bg-purple-900 { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); +} + +.bg-pink-100 { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); +} + +.bg-pink-200 { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); +} + +.bg-pink-300 { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); +} + +.bg-pink-400 { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); +} + +.bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); +} + +.bg-pink-600 { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); +} + +.bg-pink-700 { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); +} + +.bg-pink-800 { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); +} + +.bg-pink-900 { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); +} + +.hover\:bg-transparent:hover { + background-color: transparent; +} + +.hover\:bg-current:hover { + background-color: currentColor; +} + +.hover\:bg-black:hover { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); +} + +.hover\:bg-white:hover { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); +} + +.hover\:bg-gray-100:hover { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); +} + +.hover\:bg-gray-200:hover { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); +} + +.hover\:bg-gray-300:hover { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); +} + +.hover\:bg-gray-400:hover { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); +} + +.hover\:bg-gray-500:hover { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); +} + +.hover\:bg-gray-600:hover { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); +} + +.hover\:bg-gray-700:hover { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); +} + +.hover\:bg-gray-800:hover { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); +} + +.hover\:bg-gray-900:hover { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); +} + +.hover\:bg-red-100:hover { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); +} + +.hover\:bg-red-200:hover { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); +} + +.hover\:bg-red-300:hover { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); +} + +.hover\:bg-red-400:hover { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); +} + +.hover\:bg-red-500:hover { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); +} + +.hover\:bg-red-600:hover { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); +} + +.hover\:bg-red-700:hover { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); +} + +.hover\:bg-red-800:hover { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); +} + +.hover\:bg-red-900:hover { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); +} + +.hover\:bg-orange-100:hover { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); +} + +.hover\:bg-orange-200:hover { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); +} + +.hover\:bg-orange-300:hover { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); +} + +.hover\:bg-orange-400:hover { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); +} + +.hover\:bg-orange-500:hover { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); +} + +.hover\:bg-orange-600:hover { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); +} + +.hover\:bg-orange-700:hover { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); +} + +.hover\:bg-orange-800:hover { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); +} + +.hover\:bg-orange-900:hover { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); +} + +.hover\:bg-yellow-100:hover { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); +} + +.hover\:bg-yellow-200:hover { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); +} + +.hover\:bg-yellow-300:hover { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); +} + +.hover\:bg-yellow-400:hover { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); +} + +.hover\:bg-yellow-500:hover { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); +} + +.hover\:bg-yellow-600:hover { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); +} + +.hover\:bg-yellow-700:hover { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); +} + +.hover\:bg-yellow-800:hover { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); +} + +.hover\:bg-yellow-900:hover { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); +} + +.hover\:bg-green-100:hover { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); +} + +.hover\:bg-green-200:hover { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); +} + +.hover\:bg-green-300:hover { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); +} + +.hover\:bg-green-400:hover { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); +} + +.hover\:bg-green-500:hover { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); +} + +.hover\:bg-green-600:hover { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); +} + +.hover\:bg-green-700:hover { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); +} + +.hover\:bg-green-800:hover { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); +} + +.hover\:bg-green-900:hover { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); +} + +.hover\:bg-teal-100:hover { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); +} + +.hover\:bg-teal-200:hover { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); +} + +.hover\:bg-teal-300:hover { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); +} + +.hover\:bg-teal-400:hover { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); +} + +.hover\:bg-teal-500:hover { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); +} + +.hover\:bg-teal-600:hover { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); +} + +.hover\:bg-teal-700:hover { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); +} + +.hover\:bg-teal-800:hover { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); +} + +.hover\:bg-teal-900:hover { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); +} + +.hover\:bg-blue-100:hover { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); +} + +.hover\:bg-blue-200:hover { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); +} + +.hover\:bg-blue-300:hover { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); +} + +.hover\:bg-blue-400:hover { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); +} + +.hover\:bg-blue-500:hover { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); +} + +.hover\:bg-blue-600:hover { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); +} + +.hover\:bg-blue-700:hover { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); +} + +.hover\:bg-blue-800:hover { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); +} + +.hover\:bg-blue-900:hover { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); +} + +.hover\:bg-indigo-100:hover { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); +} + +.hover\:bg-indigo-200:hover { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); +} + +.hover\:bg-indigo-300:hover { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); +} + +.hover\:bg-indigo-400:hover { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); +} + +.hover\:bg-indigo-500:hover { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); +} + +.hover\:bg-indigo-600:hover { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); +} + +.hover\:bg-indigo-700:hover { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); +} + +.hover\:bg-indigo-800:hover { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); +} + +.hover\:bg-indigo-900:hover { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); +} + +.hover\:bg-purple-100:hover { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); +} + +.hover\:bg-purple-200:hover { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); +} + +.hover\:bg-purple-300:hover { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); +} + +.hover\:bg-purple-400:hover { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); +} + +.hover\:bg-purple-500:hover { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); +} + +.hover\:bg-purple-600:hover { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); +} + +.hover\:bg-purple-700:hover { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); +} + +.hover\:bg-purple-800:hover { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); +} + +.hover\:bg-purple-900:hover { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); +} + +.hover\:bg-pink-100:hover { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); +} + +.hover\:bg-pink-200:hover { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); +} + +.hover\:bg-pink-300:hover { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); +} + +.hover\:bg-pink-400:hover { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); +} + +.hover\:bg-pink-500:hover { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); +} + +.hover\:bg-pink-600:hover { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); +} + +.hover\:bg-pink-700:hover { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); +} + +.hover\:bg-pink-800:hover { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); +} + +.hover\:bg-pink-900:hover { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); +} + +.focus\:bg-transparent:focus { + background-color: transparent; +} + +.focus\:bg-current:focus { + background-color: currentColor; +} + +.focus\:bg-black:focus { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); +} + +.focus\:bg-white:focus { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); +} + +.focus\:bg-gray-100:focus { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); +} + +.focus\:bg-gray-200:focus { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); +} + +.focus\:bg-gray-300:focus { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); +} + +.focus\:bg-gray-400:focus { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); +} + +.focus\:bg-gray-500:focus { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); +} + +.focus\:bg-gray-600:focus { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); +} + +.focus\:bg-gray-700:focus { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); +} + +.focus\:bg-gray-800:focus { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); +} + +.focus\:bg-gray-900:focus { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); +} + +.focus\:bg-red-100:focus { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); +} + +.focus\:bg-red-200:focus { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); +} + +.focus\:bg-red-300:focus { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); +} + +.focus\:bg-red-400:focus { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); +} + +.focus\:bg-red-500:focus { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); +} + +.focus\:bg-red-600:focus { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); +} + +.focus\:bg-red-700:focus { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); +} + +.focus\:bg-red-800:focus { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); +} + +.focus\:bg-red-900:focus { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); +} + +.focus\:bg-orange-100:focus { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); +} + +.focus\:bg-orange-200:focus { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); +} + +.focus\:bg-orange-300:focus { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); +} + +.focus\:bg-orange-400:focus { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); +} + +.focus\:bg-orange-500:focus { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); +} + +.focus\:bg-orange-600:focus { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); +} + +.focus\:bg-orange-700:focus { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); +} + +.focus\:bg-orange-800:focus { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); +} + +.focus\:bg-orange-900:focus { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); +} + +.focus\:bg-yellow-100:focus { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); +} + +.focus\:bg-yellow-200:focus { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); +} + +.focus\:bg-yellow-300:focus { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); +} + +.focus\:bg-yellow-400:focus { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); +} + +.focus\:bg-yellow-500:focus { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); +} + +.focus\:bg-yellow-600:focus { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); +} + +.focus\:bg-yellow-700:focus { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); +} + +.focus\:bg-yellow-800:focus { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); +} + +.focus\:bg-yellow-900:focus { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); +} + +.focus\:bg-green-100:focus { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); +} + +.focus\:bg-green-200:focus { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); +} + +.focus\:bg-green-300:focus { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); +} + +.focus\:bg-green-400:focus { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); +} + +.focus\:bg-green-500:focus { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); +} + +.focus\:bg-green-600:focus { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); +} + +.focus\:bg-green-700:focus { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); +} + +.focus\:bg-green-800:focus { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); +} + +.focus\:bg-green-900:focus { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); +} + +.focus\:bg-teal-100:focus { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); +} + +.focus\:bg-teal-200:focus { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); +} + +.focus\:bg-teal-300:focus { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); +} + +.focus\:bg-teal-400:focus { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); +} + +.focus\:bg-teal-500:focus { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); +} + +.focus\:bg-teal-600:focus { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); +} + +.focus\:bg-teal-700:focus { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); +} + +.focus\:bg-teal-800:focus { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); +} + +.focus\:bg-teal-900:focus { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); +} + +.focus\:bg-blue-100:focus { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); +} + +.focus\:bg-blue-200:focus { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); +} + +.focus\:bg-blue-300:focus { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); +} + +.focus\:bg-blue-400:focus { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); +} + +.focus\:bg-blue-500:focus { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); +} + +.focus\:bg-blue-600:focus { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); +} + +.focus\:bg-blue-700:focus { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); +} + +.focus\:bg-blue-800:focus { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); +} + +.focus\:bg-blue-900:focus { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); +} + +.focus\:bg-indigo-100:focus { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); +} + +.focus\:bg-indigo-200:focus { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); +} + +.focus\:bg-indigo-300:focus { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); +} + +.focus\:bg-indigo-400:focus { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); +} + +.focus\:bg-indigo-500:focus { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); +} + +.focus\:bg-indigo-600:focus { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); +} + +.focus\:bg-indigo-700:focus { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); +} + +.focus\:bg-indigo-800:focus { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); +} + +.focus\:bg-indigo-900:focus { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); +} + +.focus\:bg-purple-100:focus { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); +} + +.focus\:bg-purple-200:focus { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); +} + +.focus\:bg-purple-300:focus { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); +} + +.focus\:bg-purple-400:focus { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); +} + +.focus\:bg-purple-500:focus { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); +} + +.focus\:bg-purple-600:focus { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); +} + +.focus\:bg-purple-700:focus { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); +} + +.focus\:bg-purple-800:focus { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); +} + +.focus\:bg-purple-900:focus { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); +} + +.focus\:bg-pink-100:focus { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); +} + +.focus\:bg-pink-200:focus { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); +} + +.focus\:bg-pink-300:focus { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); +} + +.focus\:bg-pink-400:focus { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); +} + +.focus\:bg-pink-500:focus { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); +} + +.focus\:bg-pink-600:focus { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); +} + +.focus\:bg-pink-700:focus { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); +} + +.focus\:bg-pink-800:focus { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); +} + +.focus\:bg-pink-900:focus { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); +} + +.bg-none { + background-image: none; +} + +.bg-gradient-to-t { + background-image: linear-gradient(to top, var(--gradient-color-stops)); +} + +.bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--gradient-color-stops)); +} + +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--gradient-color-stops)); +} + +.bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--gradient-color-stops)); +} + +.bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--gradient-color-stops)); +} + +.bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--gradient-color-stops)); +} + +.bg-gradient-to-l { + background-image: linear-gradient(to left, var(--gradient-color-stops)); +} + +.bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--gradient-color-stops)); +} + +.from-transparent { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.from-current { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.from-black { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.from-white { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.from-gray-100 { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.from-gray-200 { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.from-gray-300 { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.from-gray-400 { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.from-gray-500 { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.from-gray-600 { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.from-gray-700 { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.from-gray-800 { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.from-gray-900 { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.from-red-100 { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.from-red-200 { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.from-red-300 { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.from-red-400 { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.from-red-500 { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.from-red-600 { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.from-red-700 { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.from-red-800 { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.from-red-900 { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.from-orange-100 { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.from-orange-200 { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.from-orange-300 { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.from-orange-400 { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.from-orange-500 { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.from-orange-600 { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.from-orange-700 { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.from-orange-800 { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.from-orange-900 { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.from-yellow-100 { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.from-yellow-200 { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.from-yellow-300 { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.from-yellow-400 { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.from-yellow-500 { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.from-yellow-600 { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.from-yellow-700 { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.from-yellow-800 { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.from-yellow-900 { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.from-green-100 { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.from-green-200 { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.from-green-300 { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.from-green-400 { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.from-green-500 { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.from-green-600 { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.from-green-700 { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.from-green-800 { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.from-green-900 { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.from-teal-100 { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.from-teal-200 { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.from-teal-300 { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.from-teal-400 { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.from-teal-500 { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.from-teal-600 { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.from-teal-700 { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.from-teal-800 { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.from-teal-900 { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.from-blue-100 { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.from-blue-200 { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.from-blue-300 { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.from-blue-400 { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.from-blue-500 { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.from-blue-600 { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.from-blue-700 { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.from-blue-800 { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.from-blue-900 { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.from-indigo-100 { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.from-indigo-200 { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.from-indigo-300 { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.from-indigo-400 { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.from-indigo-500 { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.from-indigo-600 { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.from-indigo-700 { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.from-indigo-800 { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.from-indigo-900 { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.from-purple-100 { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.from-purple-200 { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.from-purple-300 { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.from-purple-400 { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.from-purple-500 { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.from-purple-600 { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.from-purple-700 { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.from-purple-800 { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.from-purple-900 { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.from-pink-100 { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.from-pink-200 { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.from-pink-300 { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.from-pink-400 { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.from-pink-500 { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.from-pink-600 { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.from-pink-700 { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.from-pink-800 { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.from-pink-900 { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.via-transparent { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.via-current { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.via-black { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.via-white { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.via-gray-100 { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.via-gray-200 { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.via-gray-300 { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.via-gray-400 { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.via-gray-500 { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.via-gray-600 { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.via-gray-700 { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.via-gray-800 { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.via-gray-900 { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.via-red-100 { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.via-red-200 { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.via-red-300 { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.via-red-400 { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.via-red-500 { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.via-red-600 { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.via-red-700 { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.via-red-800 { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.via-red-900 { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.via-orange-100 { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.via-orange-200 { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.via-orange-300 { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.via-orange-400 { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.via-orange-500 { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.via-orange-600 { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.via-orange-700 { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.via-orange-800 { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.via-orange-900 { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.via-yellow-100 { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.via-yellow-200 { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.via-yellow-300 { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.via-yellow-400 { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.via-yellow-500 { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.via-yellow-600 { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.via-yellow-700 { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.via-yellow-800 { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.via-yellow-900 { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.via-green-100 { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.via-green-200 { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.via-green-300 { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.via-green-400 { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.via-green-500 { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.via-green-600 { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.via-green-700 { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.via-green-800 { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.via-green-900 { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.via-teal-100 { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.via-teal-200 { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.via-teal-300 { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.via-teal-400 { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.via-teal-500 { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.via-teal-600 { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.via-teal-700 { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.via-teal-800 { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.via-teal-900 { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.via-blue-100 { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.via-blue-200 { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.via-blue-300 { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.via-blue-400 { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.via-blue-500 { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.via-blue-600 { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.via-blue-700 { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.via-blue-800 { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.via-blue-900 { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.via-indigo-100 { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.via-indigo-200 { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.via-indigo-300 { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.via-indigo-400 { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.via-indigo-500 { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.via-indigo-600 { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.via-indigo-700 { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.via-indigo-800 { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.via-indigo-900 { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.via-purple-100 { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.via-purple-200 { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.via-purple-300 { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.via-purple-400 { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.via-purple-500 { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.via-purple-600 { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.via-purple-700 { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.via-purple-800 { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.via-purple-900 { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.via-pink-100 { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.via-pink-200 { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.via-pink-300 { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.via-pink-400 { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.via-pink-500 { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.via-pink-600 { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.via-pink-700 { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.via-pink-800 { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.via-pink-900 { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.to-transparent { + --gradient-to-color: transparent; +} + +.to-current { + --gradient-to-color: currentColor; +} + +.to-black { + --gradient-to-color: #000; +} + +.to-white { + --gradient-to-color: #fff; +} + +.to-gray-100 { + --gradient-to-color: #f7fafc; +} + +.to-gray-200 { + --gradient-to-color: #edf2f7; +} + +.to-gray-300 { + --gradient-to-color: #e2e8f0; +} + +.to-gray-400 { + --gradient-to-color: #cbd5e0; +} + +.to-gray-500 { + --gradient-to-color: #a0aec0; +} + +.to-gray-600 { + --gradient-to-color: #718096; +} + +.to-gray-700 { + --gradient-to-color: #4a5568; +} + +.to-gray-800 { + --gradient-to-color: #2d3748; +} + +.to-gray-900 { + --gradient-to-color: #1a202c; +} + +.to-red-100 { + --gradient-to-color: #fff5f5; +} + +.to-red-200 { + --gradient-to-color: #fed7d7; +} + +.to-red-300 { + --gradient-to-color: #feb2b2; +} + +.to-red-400 { + --gradient-to-color: #fc8181; +} + +.to-red-500 { + --gradient-to-color: #f56565; +} + +.to-red-600 { + --gradient-to-color: #e53e3e; +} + +.to-red-700 { + --gradient-to-color: #c53030; +} + +.to-red-800 { + --gradient-to-color: #9b2c2c; +} + +.to-red-900 { + --gradient-to-color: #742a2a; +} + +.to-orange-100 { + --gradient-to-color: #fffaf0; +} + +.to-orange-200 { + --gradient-to-color: #feebc8; +} + +.to-orange-300 { + --gradient-to-color: #fbd38d; +} + +.to-orange-400 { + --gradient-to-color: #f6ad55; +} + +.to-orange-500 { + --gradient-to-color: #ed8936; +} + +.to-orange-600 { + --gradient-to-color: #dd6b20; +} + +.to-orange-700 { + --gradient-to-color: #c05621; +} + +.to-orange-800 { + --gradient-to-color: #9c4221; +} + +.to-orange-900 { + --gradient-to-color: #7b341e; +} + +.to-yellow-100 { + --gradient-to-color: #fffff0; +} + +.to-yellow-200 { + --gradient-to-color: #fefcbf; +} + +.to-yellow-300 { + --gradient-to-color: #faf089; +} + +.to-yellow-400 { + --gradient-to-color: #f6e05e; +} + +.to-yellow-500 { + --gradient-to-color: #ecc94b; +} + +.to-yellow-600 { + --gradient-to-color: #d69e2e; +} + +.to-yellow-700 { + --gradient-to-color: #b7791f; +} + +.to-yellow-800 { + --gradient-to-color: #975a16; +} + +.to-yellow-900 { + --gradient-to-color: #744210; +} + +.to-green-100 { + --gradient-to-color: #f0fff4; +} + +.to-green-200 { + --gradient-to-color: #c6f6d5; +} + +.to-green-300 { + --gradient-to-color: #9ae6b4; +} + +.to-green-400 { + --gradient-to-color: #68d391; +} + +.to-green-500 { + --gradient-to-color: #48bb78; +} + +.to-green-600 { + --gradient-to-color: #38a169; +} + +.to-green-700 { + --gradient-to-color: #2f855a; +} + +.to-green-800 { + --gradient-to-color: #276749; +} + +.to-green-900 { + --gradient-to-color: #22543d; +} + +.to-teal-100 { + --gradient-to-color: #e6fffa; +} + +.to-teal-200 { + --gradient-to-color: #b2f5ea; +} + +.to-teal-300 { + --gradient-to-color: #81e6d9; +} + +.to-teal-400 { + --gradient-to-color: #4fd1c5; +} + +.to-teal-500 { + --gradient-to-color: #38b2ac; +} + +.to-teal-600 { + --gradient-to-color: #319795; +} + +.to-teal-700 { + --gradient-to-color: #2c7a7b; +} + +.to-teal-800 { + --gradient-to-color: #285e61; +} + +.to-teal-900 { + --gradient-to-color: #234e52; +} + +.to-blue-100 { + --gradient-to-color: #ebf8ff; +} + +.to-blue-200 { + --gradient-to-color: #bee3f8; +} + +.to-blue-300 { + --gradient-to-color: #90cdf4; +} + +.to-blue-400 { + --gradient-to-color: #63b3ed; +} + +.to-blue-500 { + --gradient-to-color: #4299e1; +} + +.to-blue-600 { + --gradient-to-color: #3182ce; +} + +.to-blue-700 { + --gradient-to-color: #2b6cb0; +} + +.to-blue-800 { + --gradient-to-color: #2c5282; +} + +.to-blue-900 { + --gradient-to-color: #2a4365; +} + +.to-indigo-100 { + --gradient-to-color: #ebf4ff; +} + +.to-indigo-200 { + --gradient-to-color: #c3dafe; +} + +.to-indigo-300 { + --gradient-to-color: #a3bffa; +} + +.to-indigo-400 { + --gradient-to-color: #7f9cf5; +} + +.to-indigo-500 { + --gradient-to-color: #667eea; +} + +.to-indigo-600 { + --gradient-to-color: #5a67d8; +} + +.to-indigo-700 { + --gradient-to-color: #4c51bf; +} + +.to-indigo-800 { + --gradient-to-color: #434190; +} + +.to-indigo-900 { + --gradient-to-color: #3c366b; +} + +.to-purple-100 { + --gradient-to-color: #faf5ff; +} + +.to-purple-200 { + --gradient-to-color: #e9d8fd; +} + +.to-purple-300 { + --gradient-to-color: #d6bcfa; +} + +.to-purple-400 { + --gradient-to-color: #b794f4; +} + +.to-purple-500 { + --gradient-to-color: #9f7aea; +} + +.to-purple-600 { + --gradient-to-color: #805ad5; +} + +.to-purple-700 { + --gradient-to-color: #6b46c1; +} + +.to-purple-800 { + --gradient-to-color: #553c9a; +} + +.to-purple-900 { + --gradient-to-color: #44337a; +} + +.to-pink-100 { + --gradient-to-color: #fff5f7; +} + +.to-pink-200 { + --gradient-to-color: #fed7e2; +} + +.to-pink-300 { + --gradient-to-color: #fbb6ce; +} + +.to-pink-400 { + --gradient-to-color: #f687b3; +} + +.to-pink-500 { + --gradient-to-color: #ed64a6; +} + +.to-pink-600 { + --gradient-to-color: #d53f8c; +} + +.to-pink-700 { + --gradient-to-color: #b83280; +} + +.to-pink-800 { + --gradient-to-color: #97266d; +} + +.to-pink-900 { + --gradient-to-color: #702459; +} + +.hover\:from-transparent:hover { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.hover\:from-current:hover { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.hover\:from-black:hover { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.hover\:from-white:hover { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.hover\:from-gray-100:hover { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.hover\:from-gray-200:hover { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.hover\:from-gray-300:hover { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.hover\:from-gray-400:hover { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.hover\:from-gray-500:hover { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.hover\:from-gray-600:hover { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.hover\:from-gray-700:hover { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.hover\:from-gray-800:hover { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.hover\:from-gray-900:hover { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.hover\:from-red-100:hover { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.hover\:from-red-200:hover { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.hover\:from-red-300:hover { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.hover\:from-red-400:hover { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.hover\:from-red-500:hover { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.hover\:from-red-600:hover { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.hover\:from-red-700:hover { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.hover\:from-red-800:hover { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.hover\:from-red-900:hover { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.hover\:from-orange-100:hover { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.hover\:from-orange-200:hover { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.hover\:from-orange-300:hover { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.hover\:from-orange-400:hover { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.hover\:from-orange-500:hover { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.hover\:from-orange-600:hover { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.hover\:from-orange-700:hover { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.hover\:from-orange-800:hover { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.hover\:from-orange-900:hover { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.hover\:from-yellow-100:hover { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.hover\:from-yellow-200:hover { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.hover\:from-yellow-300:hover { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.hover\:from-yellow-400:hover { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.hover\:from-yellow-500:hover { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.hover\:from-yellow-600:hover { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.hover\:from-yellow-700:hover { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.hover\:from-yellow-800:hover { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.hover\:from-yellow-900:hover { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.hover\:from-green-100:hover { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.hover\:from-green-200:hover { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.hover\:from-green-300:hover { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.hover\:from-green-400:hover { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.hover\:from-green-500:hover { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.hover\:from-green-600:hover { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.hover\:from-green-700:hover { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.hover\:from-green-800:hover { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.hover\:from-green-900:hover { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.hover\:from-teal-100:hover { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.hover\:from-teal-200:hover { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.hover\:from-teal-300:hover { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.hover\:from-teal-400:hover { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.hover\:from-teal-500:hover { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.hover\:from-teal-600:hover { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.hover\:from-teal-700:hover { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.hover\:from-teal-800:hover { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.hover\:from-teal-900:hover { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.hover\:from-blue-100:hover { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.hover\:from-blue-200:hover { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.hover\:from-blue-300:hover { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.hover\:from-blue-400:hover { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.hover\:from-blue-500:hover { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.hover\:from-blue-600:hover { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.hover\:from-blue-700:hover { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.hover\:from-blue-800:hover { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.hover\:from-blue-900:hover { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.hover\:from-indigo-100:hover { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.hover\:from-indigo-200:hover { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.hover\:from-indigo-300:hover { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.hover\:from-indigo-400:hover { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.hover\:from-indigo-500:hover { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.hover\:from-indigo-600:hover { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.hover\:from-indigo-700:hover { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.hover\:from-indigo-800:hover { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.hover\:from-indigo-900:hover { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.hover\:from-purple-100:hover { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.hover\:from-purple-200:hover { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.hover\:from-purple-300:hover { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.hover\:from-purple-400:hover { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.hover\:from-purple-500:hover { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.hover\:from-purple-600:hover { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.hover\:from-purple-700:hover { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.hover\:from-purple-800:hover { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.hover\:from-purple-900:hover { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.hover\:from-pink-100:hover { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.hover\:from-pink-200:hover { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.hover\:from-pink-300:hover { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.hover\:from-pink-400:hover { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.hover\:from-pink-500:hover { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.hover\:from-pink-600:hover { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.hover\:from-pink-700:hover { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.hover\:from-pink-800:hover { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.hover\:from-pink-900:hover { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.hover\:via-transparent:hover { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.hover\:via-current:hover { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.hover\:via-black:hover { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.hover\:via-white:hover { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.hover\:via-gray-100:hover { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.hover\:via-gray-200:hover { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.hover\:via-gray-300:hover { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.hover\:via-gray-400:hover { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.hover\:via-gray-500:hover { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.hover\:via-gray-600:hover { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.hover\:via-gray-700:hover { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.hover\:via-gray-800:hover { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.hover\:via-gray-900:hover { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.hover\:via-red-100:hover { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.hover\:via-red-200:hover { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.hover\:via-red-300:hover { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.hover\:via-red-400:hover { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.hover\:via-red-500:hover { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.hover\:via-red-600:hover { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.hover\:via-red-700:hover { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.hover\:via-red-800:hover { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.hover\:via-red-900:hover { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.hover\:via-orange-100:hover { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.hover\:via-orange-200:hover { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.hover\:via-orange-300:hover { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.hover\:via-orange-400:hover { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.hover\:via-orange-500:hover { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.hover\:via-orange-600:hover { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.hover\:via-orange-700:hover { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.hover\:via-orange-800:hover { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.hover\:via-orange-900:hover { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.hover\:via-yellow-100:hover { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.hover\:via-yellow-200:hover { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.hover\:via-yellow-300:hover { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.hover\:via-yellow-400:hover { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.hover\:via-yellow-500:hover { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.hover\:via-yellow-600:hover { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.hover\:via-yellow-700:hover { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.hover\:via-yellow-800:hover { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.hover\:via-yellow-900:hover { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.hover\:via-green-100:hover { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.hover\:via-green-200:hover { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.hover\:via-green-300:hover { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.hover\:via-green-400:hover { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.hover\:via-green-500:hover { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.hover\:via-green-600:hover { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.hover\:via-green-700:hover { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.hover\:via-green-800:hover { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.hover\:via-green-900:hover { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.hover\:via-teal-100:hover { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.hover\:via-teal-200:hover { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.hover\:via-teal-300:hover { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.hover\:via-teal-400:hover { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.hover\:via-teal-500:hover { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.hover\:via-teal-600:hover { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.hover\:via-teal-700:hover { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.hover\:via-teal-800:hover { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.hover\:via-teal-900:hover { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.hover\:via-blue-100:hover { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.hover\:via-blue-200:hover { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.hover\:via-blue-300:hover { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.hover\:via-blue-400:hover { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.hover\:via-blue-500:hover { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.hover\:via-blue-600:hover { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.hover\:via-blue-700:hover { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.hover\:via-blue-800:hover { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.hover\:via-blue-900:hover { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.hover\:via-indigo-100:hover { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.hover\:via-indigo-200:hover { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.hover\:via-indigo-300:hover { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.hover\:via-indigo-400:hover { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.hover\:via-indigo-500:hover { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.hover\:via-indigo-600:hover { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.hover\:via-indigo-700:hover { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.hover\:via-indigo-800:hover { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.hover\:via-indigo-900:hover { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.hover\:via-purple-100:hover { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.hover\:via-purple-200:hover { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.hover\:via-purple-300:hover { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.hover\:via-purple-400:hover { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.hover\:via-purple-500:hover { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.hover\:via-purple-600:hover { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.hover\:via-purple-700:hover { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.hover\:via-purple-800:hover { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.hover\:via-purple-900:hover { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.hover\:via-pink-100:hover { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.hover\:via-pink-200:hover { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.hover\:via-pink-300:hover { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.hover\:via-pink-400:hover { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.hover\:via-pink-500:hover { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.hover\:via-pink-600:hover { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.hover\:via-pink-700:hover { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.hover\:via-pink-800:hover { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.hover\:via-pink-900:hover { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.hover\:to-transparent:hover { + --gradient-to-color: transparent; +} + +.hover\:to-current:hover { + --gradient-to-color: currentColor; +} + +.hover\:to-black:hover { + --gradient-to-color: #000; +} + +.hover\:to-white:hover { + --gradient-to-color: #fff; +} + +.hover\:to-gray-100:hover { + --gradient-to-color: #f7fafc; +} + +.hover\:to-gray-200:hover { + --gradient-to-color: #edf2f7; +} + +.hover\:to-gray-300:hover { + --gradient-to-color: #e2e8f0; +} + +.hover\:to-gray-400:hover { + --gradient-to-color: #cbd5e0; +} + +.hover\:to-gray-500:hover { + --gradient-to-color: #a0aec0; +} + +.hover\:to-gray-600:hover { + --gradient-to-color: #718096; +} + +.hover\:to-gray-700:hover { + --gradient-to-color: #4a5568; +} + +.hover\:to-gray-800:hover { + --gradient-to-color: #2d3748; +} + +.hover\:to-gray-900:hover { + --gradient-to-color: #1a202c; +} + +.hover\:to-red-100:hover { + --gradient-to-color: #fff5f5; +} + +.hover\:to-red-200:hover { + --gradient-to-color: #fed7d7; +} + +.hover\:to-red-300:hover { + --gradient-to-color: #feb2b2; +} + +.hover\:to-red-400:hover { + --gradient-to-color: #fc8181; +} + +.hover\:to-red-500:hover { + --gradient-to-color: #f56565; +} + +.hover\:to-red-600:hover { + --gradient-to-color: #e53e3e; +} + +.hover\:to-red-700:hover { + --gradient-to-color: #c53030; +} + +.hover\:to-red-800:hover { + --gradient-to-color: #9b2c2c; +} + +.hover\:to-red-900:hover { + --gradient-to-color: #742a2a; +} + +.hover\:to-orange-100:hover { + --gradient-to-color: #fffaf0; +} + +.hover\:to-orange-200:hover { + --gradient-to-color: #feebc8; +} + +.hover\:to-orange-300:hover { + --gradient-to-color: #fbd38d; +} + +.hover\:to-orange-400:hover { + --gradient-to-color: #f6ad55; +} + +.hover\:to-orange-500:hover { + --gradient-to-color: #ed8936; +} + +.hover\:to-orange-600:hover { + --gradient-to-color: #dd6b20; +} + +.hover\:to-orange-700:hover { + --gradient-to-color: #c05621; +} + +.hover\:to-orange-800:hover { + --gradient-to-color: #9c4221; +} + +.hover\:to-orange-900:hover { + --gradient-to-color: #7b341e; +} + +.hover\:to-yellow-100:hover { + --gradient-to-color: #fffff0; +} + +.hover\:to-yellow-200:hover { + --gradient-to-color: #fefcbf; +} + +.hover\:to-yellow-300:hover { + --gradient-to-color: #faf089; +} + +.hover\:to-yellow-400:hover { + --gradient-to-color: #f6e05e; +} + +.hover\:to-yellow-500:hover { + --gradient-to-color: #ecc94b; +} + +.hover\:to-yellow-600:hover { + --gradient-to-color: #d69e2e; +} + +.hover\:to-yellow-700:hover { + --gradient-to-color: #b7791f; +} + +.hover\:to-yellow-800:hover { + --gradient-to-color: #975a16; +} + +.hover\:to-yellow-900:hover { + --gradient-to-color: #744210; +} + +.hover\:to-green-100:hover { + --gradient-to-color: #f0fff4; +} + +.hover\:to-green-200:hover { + --gradient-to-color: #c6f6d5; +} + +.hover\:to-green-300:hover { + --gradient-to-color: #9ae6b4; +} + +.hover\:to-green-400:hover { + --gradient-to-color: #68d391; +} + +.hover\:to-green-500:hover { + --gradient-to-color: #48bb78; +} + +.hover\:to-green-600:hover { + --gradient-to-color: #38a169; +} + +.hover\:to-green-700:hover { + --gradient-to-color: #2f855a; +} + +.hover\:to-green-800:hover { + --gradient-to-color: #276749; +} + +.hover\:to-green-900:hover { + --gradient-to-color: #22543d; +} + +.hover\:to-teal-100:hover { + --gradient-to-color: #e6fffa; +} + +.hover\:to-teal-200:hover { + --gradient-to-color: #b2f5ea; +} + +.hover\:to-teal-300:hover { + --gradient-to-color: #81e6d9; +} + +.hover\:to-teal-400:hover { + --gradient-to-color: #4fd1c5; +} + +.hover\:to-teal-500:hover { + --gradient-to-color: #38b2ac; +} + +.hover\:to-teal-600:hover { + --gradient-to-color: #319795; +} + +.hover\:to-teal-700:hover { + --gradient-to-color: #2c7a7b; +} + +.hover\:to-teal-800:hover { + --gradient-to-color: #285e61; +} + +.hover\:to-teal-900:hover { + --gradient-to-color: #234e52; +} + +.hover\:to-blue-100:hover { + --gradient-to-color: #ebf8ff; +} + +.hover\:to-blue-200:hover { + --gradient-to-color: #bee3f8; +} + +.hover\:to-blue-300:hover { + --gradient-to-color: #90cdf4; +} + +.hover\:to-blue-400:hover { + --gradient-to-color: #63b3ed; +} + +.hover\:to-blue-500:hover { + --gradient-to-color: #4299e1; +} + +.hover\:to-blue-600:hover { + --gradient-to-color: #3182ce; +} + +.hover\:to-blue-700:hover { + --gradient-to-color: #2b6cb0; +} + +.hover\:to-blue-800:hover { + --gradient-to-color: #2c5282; +} + +.hover\:to-blue-900:hover { + --gradient-to-color: #2a4365; +} + +.hover\:to-indigo-100:hover { + --gradient-to-color: #ebf4ff; +} + +.hover\:to-indigo-200:hover { + --gradient-to-color: #c3dafe; +} + +.hover\:to-indigo-300:hover { + --gradient-to-color: #a3bffa; +} + +.hover\:to-indigo-400:hover { + --gradient-to-color: #7f9cf5; +} + +.hover\:to-indigo-500:hover { + --gradient-to-color: #667eea; +} + +.hover\:to-indigo-600:hover { + --gradient-to-color: #5a67d8; +} + +.hover\:to-indigo-700:hover { + --gradient-to-color: #4c51bf; +} + +.hover\:to-indigo-800:hover { + --gradient-to-color: #434190; +} + +.hover\:to-indigo-900:hover { + --gradient-to-color: #3c366b; +} + +.hover\:to-purple-100:hover { + --gradient-to-color: #faf5ff; +} + +.hover\:to-purple-200:hover { + --gradient-to-color: #e9d8fd; +} + +.hover\:to-purple-300:hover { + --gradient-to-color: #d6bcfa; +} + +.hover\:to-purple-400:hover { + --gradient-to-color: #b794f4; +} + +.hover\:to-purple-500:hover { + --gradient-to-color: #9f7aea; +} + +.hover\:to-purple-600:hover { + --gradient-to-color: #805ad5; +} + +.hover\:to-purple-700:hover { + --gradient-to-color: #6b46c1; +} + +.hover\:to-purple-800:hover { + --gradient-to-color: #553c9a; +} + +.hover\:to-purple-900:hover { + --gradient-to-color: #44337a; +} + +.hover\:to-pink-100:hover { + --gradient-to-color: #fff5f7; +} + +.hover\:to-pink-200:hover { + --gradient-to-color: #fed7e2; +} + +.hover\:to-pink-300:hover { + --gradient-to-color: #fbb6ce; +} + +.hover\:to-pink-400:hover { + --gradient-to-color: #f687b3; +} + +.hover\:to-pink-500:hover { + --gradient-to-color: #ed64a6; +} + +.hover\:to-pink-600:hover { + --gradient-to-color: #d53f8c; +} + +.hover\:to-pink-700:hover { + --gradient-to-color: #b83280; +} + +.hover\:to-pink-800:hover { + --gradient-to-color: #97266d; +} + +.hover\:to-pink-900:hover { + --gradient-to-color: #702459; +} + +.focus\:from-transparent:focus { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.focus\:from-current:focus { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.focus\:from-black:focus { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.focus\:from-white:focus { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.focus\:from-gray-100:focus { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.focus\:from-gray-200:focus { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.focus\:from-gray-300:focus { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.focus\:from-gray-400:focus { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.focus\:from-gray-500:focus { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.focus\:from-gray-600:focus { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.focus\:from-gray-700:focus { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.focus\:from-gray-800:focus { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.focus\:from-gray-900:focus { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.focus\:from-red-100:focus { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.focus\:from-red-200:focus { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.focus\:from-red-300:focus { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.focus\:from-red-400:focus { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.focus\:from-red-500:focus { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.focus\:from-red-600:focus { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.focus\:from-red-700:focus { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.focus\:from-red-800:focus { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.focus\:from-red-900:focus { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.focus\:from-orange-100:focus { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.focus\:from-orange-200:focus { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.focus\:from-orange-300:focus { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.focus\:from-orange-400:focus { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.focus\:from-orange-500:focus { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.focus\:from-orange-600:focus { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.focus\:from-orange-700:focus { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.focus\:from-orange-800:focus { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.focus\:from-orange-900:focus { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.focus\:from-yellow-100:focus { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.focus\:from-yellow-200:focus { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.focus\:from-yellow-300:focus { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.focus\:from-yellow-400:focus { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.focus\:from-yellow-500:focus { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.focus\:from-yellow-600:focus { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.focus\:from-yellow-700:focus { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.focus\:from-yellow-800:focus { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.focus\:from-yellow-900:focus { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.focus\:from-green-100:focus { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.focus\:from-green-200:focus { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.focus\:from-green-300:focus { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.focus\:from-green-400:focus { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.focus\:from-green-500:focus { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.focus\:from-green-600:focus { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.focus\:from-green-700:focus { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.focus\:from-green-800:focus { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.focus\:from-green-900:focus { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.focus\:from-teal-100:focus { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.focus\:from-teal-200:focus { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.focus\:from-teal-300:focus { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.focus\:from-teal-400:focus { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.focus\:from-teal-500:focus { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.focus\:from-teal-600:focus { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.focus\:from-teal-700:focus { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.focus\:from-teal-800:focus { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.focus\:from-teal-900:focus { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.focus\:from-blue-100:focus { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.focus\:from-blue-200:focus { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.focus\:from-blue-300:focus { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.focus\:from-blue-400:focus { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.focus\:from-blue-500:focus { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.focus\:from-blue-600:focus { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.focus\:from-blue-700:focus { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.focus\:from-blue-800:focus { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.focus\:from-blue-900:focus { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.focus\:from-indigo-100:focus { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.focus\:from-indigo-200:focus { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.focus\:from-indigo-300:focus { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.focus\:from-indigo-400:focus { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.focus\:from-indigo-500:focus { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.focus\:from-indigo-600:focus { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.focus\:from-indigo-700:focus { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.focus\:from-indigo-800:focus { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.focus\:from-indigo-900:focus { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.focus\:from-purple-100:focus { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.focus\:from-purple-200:focus { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.focus\:from-purple-300:focus { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.focus\:from-purple-400:focus { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.focus\:from-purple-500:focus { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.focus\:from-purple-600:focus { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.focus\:from-purple-700:focus { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.focus\:from-purple-800:focus { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.focus\:from-purple-900:focus { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.focus\:from-pink-100:focus { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.focus\:from-pink-200:focus { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.focus\:from-pink-300:focus { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.focus\:from-pink-400:focus { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.focus\:from-pink-500:focus { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.focus\:from-pink-600:focus { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.focus\:from-pink-700:focus { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.focus\:from-pink-800:focus { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.focus\:from-pink-900:focus { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.focus\:via-transparent:focus { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.focus\:via-current:focus { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.focus\:via-black:focus { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); +} + +.focus\:via-white:focus { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); +} + +.focus\:via-gray-100:focus { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); +} + +.focus\:via-gray-200:focus { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); +} + +.focus\:via-gray-300:focus { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); +} + +.focus\:via-gray-400:focus { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); +} + +.focus\:via-gray-500:focus { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); +} + +.focus\:via-gray-600:focus { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); +} + +.focus\:via-gray-700:focus { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); +} + +.focus\:via-gray-800:focus { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); +} + +.focus\:via-gray-900:focus { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); +} + +.focus\:via-red-100:focus { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); +} + +.focus\:via-red-200:focus { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); +} + +.focus\:via-red-300:focus { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); +} + +.focus\:via-red-400:focus { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); +} + +.focus\:via-red-500:focus { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); +} + +.focus\:via-red-600:focus { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); +} + +.focus\:via-red-700:focus { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); +} + +.focus\:via-red-800:focus { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); +} + +.focus\:via-red-900:focus { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); +} + +.focus\:via-orange-100:focus { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); +} + +.focus\:via-orange-200:focus { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); +} + +.focus\:via-orange-300:focus { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); +} + +.focus\:via-orange-400:focus { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); +} + +.focus\:via-orange-500:focus { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); +} + +.focus\:via-orange-600:focus { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); +} + +.focus\:via-orange-700:focus { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); +} + +.focus\:via-orange-800:focus { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); +} + +.focus\:via-orange-900:focus { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); +} + +.focus\:via-yellow-100:focus { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); +} + +.focus\:via-yellow-200:focus { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); +} + +.focus\:via-yellow-300:focus { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); +} + +.focus\:via-yellow-400:focus { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); +} + +.focus\:via-yellow-500:focus { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); +} + +.focus\:via-yellow-600:focus { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); +} + +.focus\:via-yellow-700:focus { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); +} + +.focus\:via-yellow-800:focus { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); +} + +.focus\:via-yellow-900:focus { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); +} + +.focus\:via-green-100:focus { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); +} + +.focus\:via-green-200:focus { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); +} + +.focus\:via-green-300:focus { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); +} + +.focus\:via-green-400:focus { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); +} + +.focus\:via-green-500:focus { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); +} + +.focus\:via-green-600:focus { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); +} + +.focus\:via-green-700:focus { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); +} + +.focus\:via-green-800:focus { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); +} + +.focus\:via-green-900:focus { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); +} + +.focus\:via-teal-100:focus { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); +} + +.focus\:via-teal-200:focus { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); +} + +.focus\:via-teal-300:focus { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); +} + +.focus\:via-teal-400:focus { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); +} + +.focus\:via-teal-500:focus { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); +} + +.focus\:via-teal-600:focus { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); +} + +.focus\:via-teal-700:focus { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); +} + +.focus\:via-teal-800:focus { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); +} + +.focus\:via-teal-900:focus { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); +} + +.focus\:via-blue-100:focus { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); +} + +.focus\:via-blue-200:focus { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); +} + +.focus\:via-blue-300:focus { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); +} + +.focus\:via-blue-400:focus { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); +} + +.focus\:via-blue-500:focus { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); +} + +.focus\:via-blue-600:focus { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); +} + +.focus\:via-blue-700:focus { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); +} + +.focus\:via-blue-800:focus { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); +} + +.focus\:via-blue-900:focus { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); +} + +.focus\:via-indigo-100:focus { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); +} + +.focus\:via-indigo-200:focus { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); +} + +.focus\:via-indigo-300:focus { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); +} + +.focus\:via-indigo-400:focus { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); +} + +.focus\:via-indigo-500:focus { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); +} + +.focus\:via-indigo-600:focus { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); +} + +.focus\:via-indigo-700:focus { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); +} + +.focus\:via-indigo-800:focus { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); +} + +.focus\:via-indigo-900:focus { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); +} + +.focus\:via-purple-100:focus { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); +} + +.focus\:via-purple-200:focus { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); +} + +.focus\:via-purple-300:focus { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); +} + +.focus\:via-purple-400:focus { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); +} + +.focus\:via-purple-500:focus { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); +} + +.focus\:via-purple-600:focus { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); +} + +.focus\:via-purple-700:focus { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); +} + +.focus\:via-purple-800:focus { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); +} + +.focus\:via-purple-900:focus { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); +} + +.focus\:via-pink-100:focus { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); +} + +.focus\:via-pink-200:focus { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); +} + +.focus\:via-pink-300:focus { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); +} + +.focus\:via-pink-400:focus { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); +} + +.focus\:via-pink-500:focus { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); +} + +.focus\:via-pink-600:focus { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); +} + +.focus\:via-pink-700:focus { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); +} + +.focus\:via-pink-800:focus { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); +} + +.focus\:via-pink-900:focus { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); +} + +.focus\:to-transparent:focus { + --gradient-to-color: transparent; +} + +.focus\:to-current:focus { + --gradient-to-color: currentColor; +} + +.focus\:to-black:focus { + --gradient-to-color: #000; +} + +.focus\:to-white:focus { + --gradient-to-color: #fff; +} + +.focus\:to-gray-100:focus { + --gradient-to-color: #f7fafc; +} + +.focus\:to-gray-200:focus { + --gradient-to-color: #edf2f7; +} + +.focus\:to-gray-300:focus { + --gradient-to-color: #e2e8f0; +} + +.focus\:to-gray-400:focus { + --gradient-to-color: #cbd5e0; +} + +.focus\:to-gray-500:focus { + --gradient-to-color: #a0aec0; +} + +.focus\:to-gray-600:focus { + --gradient-to-color: #718096; +} + +.focus\:to-gray-700:focus { + --gradient-to-color: #4a5568; +} + +.focus\:to-gray-800:focus { + --gradient-to-color: #2d3748; +} + +.focus\:to-gray-900:focus { + --gradient-to-color: #1a202c; +} + +.focus\:to-red-100:focus { + --gradient-to-color: #fff5f5; +} + +.focus\:to-red-200:focus { + --gradient-to-color: #fed7d7; +} + +.focus\:to-red-300:focus { + --gradient-to-color: #feb2b2; +} + +.focus\:to-red-400:focus { + --gradient-to-color: #fc8181; +} + +.focus\:to-red-500:focus { + --gradient-to-color: #f56565; +} + +.focus\:to-red-600:focus { + --gradient-to-color: #e53e3e; +} + +.focus\:to-red-700:focus { + --gradient-to-color: #c53030; +} + +.focus\:to-red-800:focus { + --gradient-to-color: #9b2c2c; +} + +.focus\:to-red-900:focus { + --gradient-to-color: #742a2a; +} + +.focus\:to-orange-100:focus { + --gradient-to-color: #fffaf0; +} + +.focus\:to-orange-200:focus { + --gradient-to-color: #feebc8; +} + +.focus\:to-orange-300:focus { + --gradient-to-color: #fbd38d; +} + +.focus\:to-orange-400:focus { + --gradient-to-color: #f6ad55; +} + +.focus\:to-orange-500:focus { + --gradient-to-color: #ed8936; +} + +.focus\:to-orange-600:focus { + --gradient-to-color: #dd6b20; +} + +.focus\:to-orange-700:focus { + --gradient-to-color: #c05621; +} + +.focus\:to-orange-800:focus { + --gradient-to-color: #9c4221; +} + +.focus\:to-orange-900:focus { + --gradient-to-color: #7b341e; +} + +.focus\:to-yellow-100:focus { + --gradient-to-color: #fffff0; +} + +.focus\:to-yellow-200:focus { + --gradient-to-color: #fefcbf; +} + +.focus\:to-yellow-300:focus { + --gradient-to-color: #faf089; +} + +.focus\:to-yellow-400:focus { + --gradient-to-color: #f6e05e; +} + +.focus\:to-yellow-500:focus { + --gradient-to-color: #ecc94b; +} + +.focus\:to-yellow-600:focus { + --gradient-to-color: #d69e2e; +} + +.focus\:to-yellow-700:focus { + --gradient-to-color: #b7791f; +} + +.focus\:to-yellow-800:focus { + --gradient-to-color: #975a16; +} + +.focus\:to-yellow-900:focus { + --gradient-to-color: #744210; +} + +.focus\:to-green-100:focus { + --gradient-to-color: #f0fff4; +} + +.focus\:to-green-200:focus { + --gradient-to-color: #c6f6d5; +} + +.focus\:to-green-300:focus { + --gradient-to-color: #9ae6b4; +} + +.focus\:to-green-400:focus { + --gradient-to-color: #68d391; +} + +.focus\:to-green-500:focus { + --gradient-to-color: #48bb78; +} + +.focus\:to-green-600:focus { + --gradient-to-color: #38a169; +} + +.focus\:to-green-700:focus { + --gradient-to-color: #2f855a; +} + +.focus\:to-green-800:focus { + --gradient-to-color: #276749; +} + +.focus\:to-green-900:focus { + --gradient-to-color: #22543d; +} + +.focus\:to-teal-100:focus { + --gradient-to-color: #e6fffa; +} + +.focus\:to-teal-200:focus { + --gradient-to-color: #b2f5ea; +} + +.focus\:to-teal-300:focus { + --gradient-to-color: #81e6d9; +} + +.focus\:to-teal-400:focus { + --gradient-to-color: #4fd1c5; +} + +.focus\:to-teal-500:focus { + --gradient-to-color: #38b2ac; +} + +.focus\:to-teal-600:focus { + --gradient-to-color: #319795; +} + +.focus\:to-teal-700:focus { + --gradient-to-color: #2c7a7b; +} + +.focus\:to-teal-800:focus { + --gradient-to-color: #285e61; +} + +.focus\:to-teal-900:focus { + --gradient-to-color: #234e52; +} + +.focus\:to-blue-100:focus { + --gradient-to-color: #ebf8ff; +} + +.focus\:to-blue-200:focus { + --gradient-to-color: #bee3f8; +} + +.focus\:to-blue-300:focus { + --gradient-to-color: #90cdf4; +} + +.focus\:to-blue-400:focus { + --gradient-to-color: #63b3ed; +} + +.focus\:to-blue-500:focus { + --gradient-to-color: #4299e1; +} + +.focus\:to-blue-600:focus { + --gradient-to-color: #3182ce; +} + +.focus\:to-blue-700:focus { + --gradient-to-color: #2b6cb0; +} + +.focus\:to-blue-800:focus { + --gradient-to-color: #2c5282; +} + +.focus\:to-blue-900:focus { + --gradient-to-color: #2a4365; +} + +.focus\:to-indigo-100:focus { + --gradient-to-color: #ebf4ff; +} + +.focus\:to-indigo-200:focus { + --gradient-to-color: #c3dafe; +} + +.focus\:to-indigo-300:focus { + --gradient-to-color: #a3bffa; +} + +.focus\:to-indigo-400:focus { + --gradient-to-color: #7f9cf5; +} + +.focus\:to-indigo-500:focus { + --gradient-to-color: #667eea; +} + +.focus\:to-indigo-600:focus { + --gradient-to-color: #5a67d8; +} + +.focus\:to-indigo-700:focus { + --gradient-to-color: #4c51bf; +} + +.focus\:to-indigo-800:focus { + --gradient-to-color: #434190; +} + +.focus\:to-indigo-900:focus { + --gradient-to-color: #3c366b; +} + +.focus\:to-purple-100:focus { + --gradient-to-color: #faf5ff; +} + +.focus\:to-purple-200:focus { + --gradient-to-color: #e9d8fd; +} + +.focus\:to-purple-300:focus { + --gradient-to-color: #d6bcfa; +} + +.focus\:to-purple-400:focus { + --gradient-to-color: #b794f4; +} + +.focus\:to-purple-500:focus { + --gradient-to-color: #9f7aea; +} + +.focus\:to-purple-600:focus { + --gradient-to-color: #805ad5; +} + +.focus\:to-purple-700:focus { + --gradient-to-color: #6b46c1; +} + +.focus\:to-purple-800:focus { + --gradient-to-color: #553c9a; +} + +.focus\:to-purple-900:focus { + --gradient-to-color: #44337a; +} + +.focus\:to-pink-100:focus { + --gradient-to-color: #fff5f7; +} + +.focus\:to-pink-200:focus { + --gradient-to-color: #fed7e2; +} + +.focus\:to-pink-300:focus { + --gradient-to-color: #fbb6ce; +} + +.focus\:to-pink-400:focus { + --gradient-to-color: #f687b3; +} + +.focus\:to-pink-500:focus { + --gradient-to-color: #ed64a6; +} + +.focus\:to-pink-600:focus { + --gradient-to-color: #d53f8c; +} + +.focus\:to-pink-700:focus { + --gradient-to-color: #b83280; +} + +.focus\:to-pink-800:focus { + --gradient-to-color: #97266d; +} + +.focus\:to-pink-900:focus { + --gradient-to-color: #702459; +} + +.bg-opacity-0 { + --bg-opacity: 0; +} + +.bg-opacity-25 { + --bg-opacity: 0.25; +} + +.bg-opacity-50 { + --bg-opacity: 0.5; +} + +.bg-opacity-75 { + --bg-opacity: 0.75; +} + +.bg-opacity-100 { + --bg-opacity: 1; +} + +.hover\:bg-opacity-0:hover { + --bg-opacity: 0; +} + +.hover\:bg-opacity-25:hover { + --bg-opacity: 0.25; +} + +.hover\:bg-opacity-50:hover { + --bg-opacity: 0.5; +} + +.hover\:bg-opacity-75:hover { + --bg-opacity: 0.75; +} + +.hover\:bg-opacity-100:hover { + --bg-opacity: 1; +} + +.focus\:bg-opacity-0:focus { + --bg-opacity: 0; +} + +.focus\:bg-opacity-25:focus { + --bg-opacity: 0.25; +} + +.focus\:bg-opacity-50:focus { + --bg-opacity: 0.5; +} + +.focus\:bg-opacity-75:focus { + --bg-opacity: 0.75; +} + +.focus\:bg-opacity-100:focus { + --bg-opacity: 1; +} + +.bg-bottom { + background-position: bottom; +} + +.bg-center { + background-position: center; +} + +.bg-left { + background-position: left; +} + +.bg-left-bottom { + background-position: left bottom; +} + +.bg-left-top { + background-position: left top; +} + +.bg-right { + background-position: right; +} + +.bg-right-bottom { + background-position: right bottom; +} + +.bg-right-top { + background-position: right top; +} + +.bg-top { + background-position: top; +} + +.bg-repeat { + background-repeat: repeat; +} + +.bg-no-repeat { + background-repeat: no-repeat; +} + +.bg-repeat-x { + background-repeat: repeat-x; +} + +.bg-repeat-y { + background-repeat: repeat-y; +} + +.bg-repeat-round { + background-repeat: round; +} + +.bg-repeat-space { + background-repeat: space; +} + +.bg-auto { + background-size: auto; +} + +.bg-cover { + background-size: cover; +} + +.bg-contain { + background-size: contain; +} + +.border-collapse { + border-collapse: collapse; +} + +.border-separate { + border-collapse: separate; +} + +.border-transparent { + border-color: transparent; +} + +.border-current { + border-color: currentColor; +} + +.border-black { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); +} + +.border-white { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); +} + +.border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); +} + +.border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); +} + +.border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); +} + +.border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); +} + +.border-gray-500 { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); +} + +.border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); +} + +.border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); +} + +.border-gray-800 { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); +} + +.border-gray-900 { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); +} + +.border-red-100 { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); +} + +.border-red-200 { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); +} + +.border-red-300 { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); +} + +.border-red-400 { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); +} + +.border-red-500 { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); +} + +.border-red-600 { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); +} + +.border-red-700 { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); +} + +.border-red-800 { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); +} + +.border-red-900 { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); +} + +.border-orange-100 { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); +} + +.border-orange-200 { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); +} + +.border-orange-300 { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); +} + +.border-orange-400 { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); +} + +.border-orange-500 { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); +} + +.border-orange-600 { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); +} + +.border-orange-700 { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); +} + +.border-orange-800 { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); +} + +.border-orange-900 { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); +} + +.border-yellow-100 { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); +} + +.border-yellow-200 { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); +} + +.border-yellow-300 { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); +} + +.border-yellow-400 { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); +} + +.border-yellow-500 { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); +} + +.border-yellow-600 { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); +} + +.border-yellow-700 { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); +} + +.border-yellow-800 { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); +} + +.border-yellow-900 { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); +} + +.border-green-100 { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); +} + +.border-green-200 { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); +} + +.border-green-300 { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); +} + +.border-green-400 { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); +} + +.border-green-500 { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); +} + +.border-green-600 { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); +} + +.border-green-700 { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); +} + +.border-green-800 { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); +} + +.border-green-900 { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); +} + +.border-teal-100 { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); +} + +.border-teal-200 { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); +} + +.border-teal-300 { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); +} + +.border-teal-400 { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); +} + +.border-teal-500 { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); +} + +.border-teal-600 { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); +} + +.border-teal-700 { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); +} + +.border-teal-800 { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); +} + +.border-teal-900 { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); +} + +.border-blue-100 { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); +} + +.border-blue-200 { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); +} + +.border-blue-300 { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); +} + +.border-blue-400 { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); +} + +.border-blue-500 { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); +} + +.border-blue-600 { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); +} + +.border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); +} + +.border-blue-800 { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); +} + +.border-blue-900 { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); +} + +.border-indigo-100 { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); +} + +.border-indigo-200 { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); +} + +.border-indigo-300 { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); +} + +.border-indigo-400 { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); +} + +.border-indigo-500 { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); +} + +.border-indigo-600 { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); +} + +.border-indigo-700 { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); +} + +.border-indigo-800 { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); +} + +.border-indigo-900 { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); +} + +.border-purple-100 { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); +} + +.border-purple-200 { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); +} + +.border-purple-300 { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); +} + +.border-purple-400 { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); +} + +.border-purple-500 { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); +} + +.border-purple-600 { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); +} + +.border-purple-700 { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); +} + +.border-purple-800 { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); +} + +.border-purple-900 { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); +} + +.border-pink-100 { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); +} + +.border-pink-200 { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); +} + +.border-pink-300 { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); +} + +.border-pink-400 { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); +} + +.border-pink-500 { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); +} + +.border-pink-600 { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); +} + +.border-pink-700 { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); +} + +.border-pink-800 { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); +} + +.border-pink-900 { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); +} + +.hover\:border-transparent:hover { + border-color: transparent; +} + +.hover\:border-current:hover { + border-color: currentColor; +} + +.hover\:border-black:hover { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); +} + +.hover\:border-white:hover { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); +} + +.hover\:border-gray-100:hover { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); +} + +.hover\:border-gray-200:hover { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); +} + +.hover\:border-gray-300:hover { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); +} + +.hover\:border-gray-400:hover { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); +} + +.hover\:border-gray-500:hover { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); +} + +.hover\:border-gray-600:hover { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); +} + +.hover\:border-gray-700:hover { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); +} + +.hover\:border-gray-800:hover { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); +} + +.hover\:border-gray-900:hover { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); +} + +.hover\:border-red-100:hover { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); +} + +.hover\:border-red-200:hover { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); +} + +.hover\:border-red-300:hover { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); +} + +.hover\:border-red-400:hover { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); +} + +.hover\:border-red-500:hover { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); +} + +.hover\:border-red-600:hover { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); +} + +.hover\:border-red-700:hover { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); +} + +.hover\:border-red-800:hover { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); +} + +.hover\:border-red-900:hover { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); +} + +.hover\:border-orange-100:hover { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); +} + +.hover\:border-orange-200:hover { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); +} + +.hover\:border-orange-300:hover { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); +} + +.hover\:border-orange-400:hover { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); +} + +.hover\:border-orange-500:hover { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); +} + +.hover\:border-orange-600:hover { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); +} + +.hover\:border-orange-700:hover { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); +} + +.hover\:border-orange-800:hover { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); +} + +.hover\:border-orange-900:hover { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); +} + +.hover\:border-yellow-100:hover { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); +} + +.hover\:border-yellow-200:hover { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); +} + +.hover\:border-yellow-300:hover { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); +} + +.hover\:border-yellow-400:hover { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); +} + +.hover\:border-yellow-500:hover { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); +} + +.hover\:border-yellow-600:hover { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); +} + +.hover\:border-yellow-700:hover { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); +} + +.hover\:border-yellow-800:hover { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); +} + +.hover\:border-yellow-900:hover { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); +} + +.hover\:border-green-100:hover { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); +} + +.hover\:border-green-200:hover { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); +} + +.hover\:border-green-300:hover { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); +} + +.hover\:border-green-400:hover { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); +} + +.hover\:border-green-500:hover { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); +} + +.hover\:border-green-600:hover { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); +} + +.hover\:border-green-700:hover { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); +} + +.hover\:border-green-800:hover { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); +} + +.hover\:border-green-900:hover { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); +} + +.hover\:border-teal-100:hover { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); +} + +.hover\:border-teal-200:hover { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); +} + +.hover\:border-teal-300:hover { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); +} + +.hover\:border-teal-400:hover { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); +} + +.hover\:border-teal-500:hover { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); +} + +.hover\:border-teal-600:hover { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); +} + +.hover\:border-teal-700:hover { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); +} + +.hover\:border-teal-800:hover { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); +} + +.hover\:border-teal-900:hover { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); +} + +.hover\:border-blue-100:hover { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); +} + +.hover\:border-blue-200:hover { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); +} + +.hover\:border-blue-300:hover { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); +} + +.hover\:border-blue-400:hover { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); +} + +.hover\:border-blue-500:hover { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); +} + +.hover\:border-blue-600:hover { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); +} + +.hover\:border-blue-700:hover { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); +} + +.hover\:border-blue-800:hover { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); +} + +.hover\:border-blue-900:hover { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); +} + +.hover\:border-indigo-100:hover { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); +} + +.hover\:border-indigo-200:hover { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); +} + +.hover\:border-indigo-300:hover { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); +} + +.hover\:border-indigo-400:hover { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); +} + +.hover\:border-indigo-500:hover { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); +} + +.hover\:border-indigo-600:hover { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); +} + +.hover\:border-indigo-700:hover { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); +} + +.hover\:border-indigo-800:hover { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); +} + +.hover\:border-indigo-900:hover { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); +} + +.hover\:border-purple-100:hover { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); +} + +.hover\:border-purple-200:hover { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); +} + +.hover\:border-purple-300:hover { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); +} + +.hover\:border-purple-400:hover { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); +} + +.hover\:border-purple-500:hover { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); +} + +.hover\:border-purple-600:hover { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); +} + +.hover\:border-purple-700:hover { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); +} + +.hover\:border-purple-800:hover { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); +} + +.hover\:border-purple-900:hover { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); +} + +.hover\:border-pink-100:hover { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); +} + +.hover\:border-pink-200:hover { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); +} + +.hover\:border-pink-300:hover { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); +} + +.hover\:border-pink-400:hover { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); +} + +.hover\:border-pink-500:hover { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); +} + +.hover\:border-pink-600:hover { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); +} + +.hover\:border-pink-700:hover { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); +} + +.hover\:border-pink-800:hover { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); +} + +.hover\:border-pink-900:hover { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); +} + +.focus\:border-transparent:focus { + border-color: transparent; +} + +.focus\:border-current:focus { + border-color: currentColor; +} + +.focus\:border-black:focus { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); +} + +.focus\:border-white:focus { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); +} + +.focus\:border-gray-100:focus { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); +} + +.focus\:border-gray-200:focus { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); +} + +.focus\:border-gray-300:focus { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); +} + +.focus\:border-gray-400:focus { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); +} + +.focus\:border-gray-500:focus { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); +} + +.focus\:border-gray-600:focus { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); +} + +.focus\:border-gray-700:focus { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); +} + +.focus\:border-gray-800:focus { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); +} + +.focus\:border-gray-900:focus { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); +} + +.focus\:border-red-100:focus { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); +} + +.focus\:border-red-200:focus { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); +} + +.focus\:border-red-300:focus { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); +} + +.focus\:border-red-400:focus { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); +} + +.focus\:border-red-500:focus { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); +} + +.focus\:border-red-600:focus { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); +} + +.focus\:border-red-700:focus { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); +} + +.focus\:border-red-800:focus { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); +} + +.focus\:border-red-900:focus { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); +} + +.focus\:border-orange-100:focus { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); +} + +.focus\:border-orange-200:focus { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); +} + +.focus\:border-orange-300:focus { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); +} + +.focus\:border-orange-400:focus { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); +} + +.focus\:border-orange-500:focus { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); +} + +.focus\:border-orange-600:focus { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); +} + +.focus\:border-orange-700:focus { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); +} + +.focus\:border-orange-800:focus { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); +} + +.focus\:border-orange-900:focus { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); +} + +.focus\:border-yellow-100:focus { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); +} + +.focus\:border-yellow-200:focus { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); +} + +.focus\:border-yellow-300:focus { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); +} + +.focus\:border-yellow-400:focus { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); +} + +.focus\:border-yellow-500:focus { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); +} + +.focus\:border-yellow-600:focus { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); +} + +.focus\:border-yellow-700:focus { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); +} + +.focus\:border-yellow-800:focus { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); +} + +.focus\:border-yellow-900:focus { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); +} + +.focus\:border-green-100:focus { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); +} + +.focus\:border-green-200:focus { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); +} + +.focus\:border-green-300:focus { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); +} + +.focus\:border-green-400:focus { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); +} + +.focus\:border-green-500:focus { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); +} + +.focus\:border-green-600:focus { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); +} + +.focus\:border-green-700:focus { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); +} + +.focus\:border-green-800:focus { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); +} + +.focus\:border-green-900:focus { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); +} + +.focus\:border-teal-100:focus { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); +} + +.focus\:border-teal-200:focus { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); +} + +.focus\:border-teal-300:focus { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); +} + +.focus\:border-teal-400:focus { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); +} + +.focus\:border-teal-500:focus { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); +} + +.focus\:border-teal-600:focus { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); +} + +.focus\:border-teal-700:focus { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); +} + +.focus\:border-teal-800:focus { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); +} + +.focus\:border-teal-900:focus { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); +} + +.focus\:border-blue-100:focus { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); +} + +.focus\:border-blue-200:focus { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); +} + +.focus\:border-blue-300:focus { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); +} + +.focus\:border-blue-400:focus { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); +} + +.focus\:border-blue-500:focus { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); +} + +.focus\:border-blue-600:focus { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); +} + +.focus\:border-blue-700:focus { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); +} + +.focus\:border-blue-800:focus { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); +} + +.focus\:border-blue-900:focus { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); +} + +.focus\:border-indigo-100:focus { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); +} + +.focus\:border-indigo-200:focus { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); +} + +.focus\:border-indigo-300:focus { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); +} + +.focus\:border-indigo-400:focus { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); +} + +.focus\:border-indigo-500:focus { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); +} + +.focus\:border-indigo-600:focus { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); +} + +.focus\:border-indigo-700:focus { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); +} + +.focus\:border-indigo-800:focus { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); +} + +.focus\:border-indigo-900:focus { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); +} + +.focus\:border-purple-100:focus { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); +} + +.focus\:border-purple-200:focus { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); +} + +.focus\:border-purple-300:focus { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); +} + +.focus\:border-purple-400:focus { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); +} + +.focus\:border-purple-500:focus { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); +} + +.focus\:border-purple-600:focus { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); +} + +.focus\:border-purple-700:focus { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); +} + +.focus\:border-purple-800:focus { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); +} + +.focus\:border-purple-900:focus { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); +} + +.focus\:border-pink-100:focus { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); +} + +.focus\:border-pink-200:focus { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); +} + +.focus\:border-pink-300:focus { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); +} + +.focus\:border-pink-400:focus { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); +} + +.focus\:border-pink-500:focus { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); +} + +.focus\:border-pink-600:focus { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); +} + +.focus\:border-pink-700:focus { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); +} + +.focus\:border-pink-800:focus { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); +} + +.focus\:border-pink-900:focus { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); +} + +.border-opacity-0 { + --border-opacity: 0; +} + +.border-opacity-25 { + --border-opacity: 0.25; +} + +.border-opacity-50 { + --border-opacity: 0.5; +} + +.border-opacity-75 { + --border-opacity: 0.75; +} + +.border-opacity-100 { + --border-opacity: 1; +} + +.hover\:border-opacity-0:hover { + --border-opacity: 0; +} + +.hover\:border-opacity-25:hover { + --border-opacity: 0.25; +} + +.hover\:border-opacity-50:hover { + --border-opacity: 0.5; +} + +.hover\:border-opacity-75:hover { + --border-opacity: 0.75; +} + +.hover\:border-opacity-100:hover { + --border-opacity: 1; +} + +.focus\:border-opacity-0:focus { + --border-opacity: 0; +} + +.focus\:border-opacity-25:focus { + --border-opacity: 0.25; +} + +.focus\:border-opacity-50:focus { + --border-opacity: 0.5; +} + +.focus\:border-opacity-75:focus { + --border-opacity: 0.75; +} + +.focus\:border-opacity-100:focus { + --border-opacity: 1; +} + +.rounded-none { + border-radius: 0; +} + +.rounded-sm { + border-radius: 0.125rem; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.rounded-r-none { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.rounded-b-none { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.rounded-l-none { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.rounded-t-sm { + border-top-left-radius: 0.125rem; + border-top-right-radius: 0.125rem; +} + +.rounded-r-sm { + border-top-right-radius: 0.125rem; + border-bottom-right-radius: 0.125rem; +} + +.rounded-b-sm { + border-bottom-right-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; +} + +.rounded-l-sm { + border-top-left-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; +} + +.rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.rounded-r { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.rounded-t-md { + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.rounded-b-md { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +.rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px; +} + +.rounded-r-full { + border-top-right-radius: 9999px; + border-bottom-right-radius: 9999px; +} + +.rounded-b-full { + border-bottom-right-radius: 9999px; + border-bottom-left-radius: 9999px; +} + +.rounded-l-full { + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; +} + +.rounded-tl-none { + border-top-left-radius: 0; +} + +.rounded-tr-none { + border-top-right-radius: 0; +} + +.rounded-br-none { + border-bottom-right-radius: 0; +} + +.rounded-bl-none { + border-bottom-left-radius: 0; +} + +.rounded-tl-sm { + border-top-left-radius: 0.125rem; +} + +.rounded-tr-sm { + border-top-right-radius: 0.125rem; +} + +.rounded-br-sm { + border-bottom-right-radius: 0.125rem; +} + +.rounded-bl-sm { + border-bottom-left-radius: 0.125rem; +} + +.rounded-tl { + border-top-left-radius: 0.25rem; +} + +.rounded-tr { + border-top-right-radius: 0.25rem; +} + +.rounded-br { + border-bottom-right-radius: 0.25rem; +} + +.rounded-bl { + border-bottom-left-radius: 0.25rem; +} + +.rounded-tl-md { + border-top-left-radius: 0.375rem; +} + +.rounded-tr-md { + border-top-right-radius: 0.375rem; +} + +.rounded-br-md { + border-bottom-right-radius: 0.375rem; +} + +.rounded-bl-md { + border-bottom-left-radius: 0.375rem; +} + +.rounded-tl-lg { + border-top-left-radius: 0.5rem; +} + +.rounded-tr-lg { + border-top-right-radius: 0.5rem; +} + +.rounded-br-lg { + border-bottom-right-radius: 0.5rem; +} + +.rounded-bl-lg { + border-bottom-left-radius: 0.5rem; +} + +.rounded-tl-full { + border-top-left-radius: 9999px; +} + +.rounded-tr-full { + border-top-right-radius: 9999px; +} + +.rounded-br-full { + border-bottom-right-radius: 9999px; +} + +.rounded-bl-full { + border-bottom-left-radius: 9999px; +} + +.border-solid { + border-style: solid; +} + +.border-dashed { + border-style: dashed; +} + +.border-dotted { + border-style: dotted; +} + +.border-double { + border-style: double; +} + +.border-none { + border-style: none; +} + +.border-0 { + border-width: 0; +} + +.border-2 { + border-width: 2px; +} + +.border-4 { + border-width: 4px; +} + +.border-8 { + border-width: 8px; +} + +.border { + border-width: 1px; +} + +.border-t-0 { + border-top-width: 0; +} + +.border-r-0 { + border-right-width: 0; +} + +.border-b-0 { + border-bottom-width: 0; +} + +.border-l-0 { + border-left-width: 0; +} + +.border-t-2 { + border-top-width: 2px; +} + +.border-r-2 { + border-right-width: 2px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-l-2 { + border-left-width: 2px; +} + +.border-t-4 { + border-top-width: 4px; +} + +.border-r-4 { + border-right-width: 4px; +} + +.border-b-4 { + border-bottom-width: 4px; +} + +.border-l-4 { + border-left-width: 4px; +} + +.border-t-8 { + border-top-width: 8px; +} + +.border-r-8 { + border-right-width: 8px; +} + +.border-b-8 { + border-bottom-width: 8px; +} + +.border-l-8 { + border-left-width: 8px; +} + +.border-t { + border-top-width: 1px; +} + +.border-r { + border-right-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-l { + border-left-width: 1px; +} + +.box-border { + box-sizing: border-box; +} + +.box-content { + box-sizing: content-box; +} + +.cursor-auto { + cursor: auto; +} + +.cursor-default { + cursor: default; +} + +.cursor-pointer { + cursor: pointer; +} + +.cursor-wait { + cursor: wait; +} + +.cursor-text { + cursor: text; +} + +.cursor-move { + cursor: move; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.table-caption { + display: table-caption; +} + +.table-cell { + display: table-cell; +} + +.table-column { + display: table-column; +} + +.table-column-group { + display: table-column-group; +} + +.table-footer-group { + display: table-footer-group; +} + +.table-header-group { + display: table-header-group; +} + +.table-row-group { + display: table-row-group; +} + +.table-row { + display: table-row; +} + +.flow-root { + display: flow-root; +} + +.grid { + display: grid; +} + +.inline-grid { + display: inline-grid; +} + +.contents { + display: contents; +} + +.hidden { + display: none; +} + +.flex-row { + flex-direction: row; +} + +.flex-row-reverse { + flex-direction: row-reverse; +} + +.flex-col { + flex-direction: column; +} + +.flex-col-reverse { + flex-direction: column-reverse; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.flex-wrap-reverse { + flex-wrap: wrap-reverse; +} + +.flex-no-wrap { + flex-wrap: nowrap; +} + +.place-items-auto { + place-items: auto; +} + +.place-items-start { + place-items: start; +} + +.place-items-end { + place-items: end; +} + +.place-items-center { + place-items: center; +} + +.place-items-stretch { + place-items: stretch; +} + +.place-content-center { + place-content: center; +} + +.place-content-start { + place-content: start; +} + +.place-content-end { + place-content: end; +} + +.place-content-between { + place-content: space-between; +} + +.place-content-around { + place-content: space-around; +} + +.place-content-evenly { + place-content: space-evenly; +} + +.place-content-stretch { + place-content: stretch; +} + +.place-self-auto { + place-self: auto; +} + +.place-self-start { + place-self: start; +} + +.place-self-end { + place-self: end; +} + +.place-self-center { + place-self: center; +} + +.place-self-stretch { + place-self: stretch; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.items-baseline { + align-items: baseline; +} + +.items-stretch { + align-items: stretch; +} + +.content-center { + align-content: center; +} + +.content-start { + align-content: flex-start; +} + +.content-end { + align-content: flex-end; +} + +.content-between { + align-content: space-between; +} + +.content-around { + align-content: space-around; +} + +.content-evenly { + align-content: space-evenly; +} + +.self-auto { + align-self: auto; +} + +.self-start { + align-self: flex-start; +} + +.self-end { + align-self: flex-end; +} + +.self-center { + align-self: center; +} + +.self-stretch { + align-self: stretch; +} + +.justify-items-auto { + justify-items: auto; +} + +.justify-items-start { + justify-items: start; +} + +.justify-items-end { + justify-items: end; +} + +.justify-items-center { + justify-items: center; +} + +.justify-items-stretch { + justify-items: stretch; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-around { + justify-content: space-around; +} + +.justify-evenly { + justify-content: space-evenly; +} + +.justify-self-auto { + justify-self: auto; +} + +.justify-self-start { + justify-self: start; +} + +.justify-self-end { + justify-self: end; +} + +.justify-self-center { + justify-self: center; +} + +.justify-self-stretch { + justify-self: stretch; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-auto { + flex: 1 1 auto; +} + +.flex-initial { + flex: 0 1 auto; +} + +.flex-none { + flex: none; +} + +.flex-grow-0 { + flex-grow: 0; +} + +.flex-grow { + flex-grow: 1; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.flex-shrink { + flex-shrink: 1; +} + +.order-1 { + order: 1; +} + +.order-2 { + order: 2; +} + +.order-3 { + order: 3; +} + +.order-4 { + order: 4; +} + +.order-5 { + order: 5; +} + +.order-6 { + order: 6; +} + +.order-7 { + order: 7; +} + +.order-8 { + order: 8; +} + +.order-9 { + order: 9; +} + +.order-10 { + order: 10; +} + +.order-11 { + order: 11; +} + +.order-12 { + order: 12; +} + +.order-first { + order: -9999; +} + +.order-last { + order: 9999; +} + +.order-none { + order: 0; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.float-none { + float: none; +} + +.clearfix:after { + content: ""; + display: table; + clear: both; +} + +.clear-left { + clear: left; +} + +.clear-right { + clear: right; +} + +.clear-both { + clear: both; +} + +.clear-none { + clear: none; +} + +.font-sans { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +.font-serif { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; +} + +.font-mono { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.font-hairline { + font-weight: 100; +} + +.font-thin { + font-weight: 200; +} + +.font-light { + font-weight: 300; +} + +.font-normal { + font-weight: 400; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} + +.font-extrabold { + font-weight: 800; +} + +.font-black { + font-weight: 900; +} + +.hover\:font-hairline:hover { + font-weight: 100; +} + +.hover\:font-thin:hover { + font-weight: 200; +} + +.hover\:font-light:hover { + font-weight: 300; +} + +.hover\:font-normal:hover { + font-weight: 400; +} + +.hover\:font-medium:hover { + font-weight: 500; +} + +.hover\:font-semibold:hover { + font-weight: 600; +} + +.hover\:font-bold:hover { + font-weight: 700; +} + +.hover\:font-extrabold:hover { + font-weight: 800; +} + +.hover\:font-black:hover { + font-weight: 900; +} + +.focus\:font-hairline:focus { + font-weight: 100; +} + +.focus\:font-thin:focus { + font-weight: 200; +} + +.focus\:font-light:focus { + font-weight: 300; +} + +.focus\:font-normal:focus { + font-weight: 400; +} + +.focus\:font-medium:focus { + font-weight: 500; +} + +.focus\:font-semibold:focus { + font-weight: 600; +} + +.focus\:font-bold:focus { + font-weight: 700; +} + +.focus\:font-extrabold:focus { + font-weight: 800; +} + +.focus\:font-black:focus { + font-weight: 900; +} + +.h-0 { + height: 0; +} + +.h-1 { + height: 0.25rem; +} + +.h-2 { + height: 0.5rem; +} + +.h-3 { + height: 0.75rem; +} + +.h-4 { + height: 1rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-8 { + height: 2rem; +} + +.h-10 { + height: 2.5rem; +} + +.h-12 { + height: 3rem; +} + +.h-16 { + height: 4rem; +} + +.h-20 { + height: 5rem; +} + +.h-24 { + height: 6rem; +} + +.h-32 { + height: 8rem; +} + +.h-40 { + height: 10rem; +} + +.h-48 { + height: 12rem; +} + +.h-56 { + height: 14rem; +} + +.h-64 { + height: 16rem; +} + +.h-auto { + height: auto; +} + +.h-px { + height: 1px; +} + +.h-full { + height: 100%; +} + +.h-screen { + height: 100vh; +} + +.text-xs { + font-size: 0.75rem; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-base { + font-size: 1rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.text-xl { + font-size: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; +} + +.text-3xl { + font-size: 1.875rem; +} + +.text-4xl { + font-size: 2.25rem; +} + +.text-5xl { + font-size: 3rem; +} + +.text-6xl { + font-size: 4rem; +} + +.leading-3 { + line-height: .75rem; +} + +.leading-4 { + line-height: 1rem; +} + +.leading-5 { + line-height: 1.25rem; +} + +.leading-6 { + line-height: 1.5rem; +} + +.leading-7 { + line-height: 1.75rem; +} + +.leading-8 { + line-height: 2rem; +} + +.leading-9 { + line-height: 2.25rem; +} + +.leading-10 { + line-height: 2.5rem; +} + +.leading-none { + line-height: 1; +} + +.leading-tight { + line-height: 1.25; +} + +.leading-snug { + line-height: 1.375; +} + +.leading-normal { + line-height: 1.5; +} + +.leading-relaxed { + line-height: 1.625; +} + +.leading-loose { + line-height: 2; +} + +.list-inside { + list-style-position: inside; +} + +.list-outside { + list-style-position: outside; +} + +.list-none { + list-style-type: none; +} + +.list-disc { + list-style-type: disc; +} + +.list-decimal { + list-style-type: decimal; +} + +.m-0 { + margin: 0; +} + +.m-1 { + margin: 0.25rem; +} + +.m-2 { + margin: 0.5rem; +} + +.m-3 { + margin: 0.75rem; +} + +.m-4 { + margin: 1rem; +} + +.m-5 { + margin: 1.25rem; +} + +.m-6 { + margin: 1.5rem; +} + +.m-8 { + margin: 2rem; +} + +.m-10 { + margin: 2.5rem; +} + +.m-12 { + margin: 3rem; +} + +.m-16 { + margin: 4rem; +} + +.m-20 { + margin: 5rem; +} + +.m-24 { + margin: 6rem; +} + +.m-32 { + margin: 8rem; +} + +.m-40 { + margin: 10rem; +} + +.m-48 { + margin: 12rem; +} + +.m-56 { + margin: 14rem; +} + +.m-64 { + margin: 16rem; +} + +.m-auto { + margin: auto; +} + +.m-px { + margin: 1px; +} + +.-m-1 { + margin: -0.25rem; +} + +.-m-2 { + margin: -0.5rem; +} + +.-m-3 { + margin: -0.75rem; +} + +.-m-4 { + margin: -1rem; +} + +.-m-5 { + margin: -1.25rem; +} + +.-m-6 { + margin: -1.5rem; +} + +.-m-8 { + margin: -2rem; +} + +.-m-10 { + margin: -2.5rem; +} + +.-m-12 { + margin: -3rem; +} + +.-m-16 { + margin: -4rem; +} + +.-m-20 { + margin: -5rem; +} + +.-m-24 { + margin: -6rem; +} + +.-m-32 { + margin: -8rem; +} + +.-m-40 { + margin: -10rem; +} + +.-m-48 { + margin: -12rem; +} + +.-m-56 { + margin: -14rem; +} + +.-m-64 { + margin: -16rem; +} + +.-m-px { + margin: -1px; +} + +.my-0 { + margin-top: 0; + margin-bottom: 0; +} + +.mx-0 { + margin-left: 0; + margin-right: 0; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +.mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + +.my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + +.mx-5 { + margin-left: 1.25rem; + margin-right: 1.25rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; +} + +.my-8 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +.mx-8 { + margin-left: 2rem; + margin-right: 2rem; +} + +.my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; +} + +.mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; +} + +.my-12 { + margin-top: 3rem; + margin-bottom: 3rem; +} + +.mx-12 { + margin-left: 3rem; + margin-right: 3rem; +} + +.my-16 { + margin-top: 4rem; + margin-bottom: 4rem; +} + +.mx-16 { + margin-left: 4rem; + margin-right: 4rem; +} + +.my-20 { + margin-top: 5rem; + margin-bottom: 5rem; +} + +.mx-20 { + margin-left: 5rem; + margin-right: 5rem; +} + +.my-24 { + margin-top: 6rem; + margin-bottom: 6rem; +} + +.mx-24 { + margin-left: 6rem; + margin-right: 6rem; +} + +.my-32 { + margin-top: 8rem; + margin-bottom: 8rem; +} + +.mx-32 { + margin-left: 8rem; + margin-right: 8rem; +} + +.my-40 { + margin-top: 10rem; + margin-bottom: 10rem; +} + +.mx-40 { + margin-left: 10rem; + margin-right: 10rem; +} + +.my-48 { + margin-top: 12rem; + margin-bottom: 12rem; +} + +.mx-48 { + margin-left: 12rem; + margin-right: 12rem; +} + +.my-56 { + margin-top: 14rem; + margin-bottom: 14rem; +} + +.mx-56 { + margin-left: 14rem; + margin-right: 14rem; +} + +.my-64 { + margin-top: 16rem; + margin-bottom: 16rem; +} + +.mx-64 { + margin-left: 16rem; + margin-right: 16rem; +} + +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-px { + margin-top: 1px; + margin-bottom: 1px; +} + +.mx-px { + margin-left: 1px; + margin-right: 1px; +} + +.-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; +} + +.-mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; +} + +.-my-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; +} + +.-mx-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; +} + +.-my-3 { + margin-top: -0.75rem; + margin-bottom: -0.75rem; +} + +.-mx-3 { + margin-left: -0.75rem; + margin-right: -0.75rem; +} + +.-my-4 { + margin-top: -1rem; + margin-bottom: -1rem; +} + +.-mx-4 { + margin-left: -1rem; + margin-right: -1rem; +} + +.-my-5 { + margin-top: -1.25rem; + margin-bottom: -1.25rem; +} + +.-mx-5 { + margin-left: -1.25rem; + margin-right: -1.25rem; +} + +.-my-6 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; +} + +.-mx-6 { + margin-left: -1.5rem; + margin-right: -1.5rem; +} + +.-my-8 { + margin-top: -2rem; + margin-bottom: -2rem; +} + +.-mx-8 { + margin-left: -2rem; + margin-right: -2rem; +} + +.-my-10 { + margin-top: -2.5rem; + margin-bottom: -2.5rem; +} + +.-mx-10 { + margin-left: -2.5rem; + margin-right: -2.5rem; +} + +.-my-12 { + margin-top: -3rem; + margin-bottom: -3rem; +} + +.-mx-12 { + margin-left: -3rem; + margin-right: -3rem; +} + +.-my-16 { + margin-top: -4rem; + margin-bottom: -4rem; +} + +.-mx-16 { + margin-left: -4rem; + margin-right: -4rem; +} + +.-my-20 { + margin-top: -5rem; + margin-bottom: -5rem; +} + +.-mx-20 { + margin-left: -5rem; + margin-right: -5rem; +} + +.-my-24 { + margin-top: -6rem; + margin-bottom: -6rem; +} + +.-mx-24 { + margin-left: -6rem; + margin-right: -6rem; +} + +.-my-32 { + margin-top: -8rem; + margin-bottom: -8rem; +} + +.-mx-32 { + margin-left: -8rem; + margin-right: -8rem; +} + +.-my-40 { + margin-top: -10rem; + margin-bottom: -10rem; +} + +.-mx-40 { + margin-left: -10rem; + margin-right: -10rem; +} + +.-my-48 { + margin-top: -12rem; + margin-bottom: -12rem; +} + +.-mx-48 { + margin-left: -12rem; + margin-right: -12rem; +} + +.-my-56 { + margin-top: -14rem; + margin-bottom: -14rem; +} + +.-mx-56 { + margin-left: -14rem; + margin-right: -14rem; +} + +.-my-64 { + margin-top: -16rem; + margin-bottom: -16rem; +} + +.-mx-64 { + margin-left: -16rem; + margin-right: -16rem; +} + +.-my-px { + margin-top: -1px; + margin-bottom: -1px; +} + +.-mx-px { + margin-left: -1px; + margin-right: -1px; +} + +.mt-0 { + margin-top: 0; +} + +.mr-0 { + margin-right: 0; +} + +.mb-0 { + margin-bottom: 0; +} + +.ml-0 { + margin-left: 0; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.ml-4 { + margin-left: 1rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.mr-5 { + margin-right: 1.25rem; +} + +.mb-5 { + margin-bottom: 1.25rem; +} + +.ml-5 { + margin-left: 1.25rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mr-6 { + margin-right: 1.5rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.ml-6 { + margin-left: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.mr-8 { + margin-right: 2rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.ml-8 { + margin-left: 2rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mr-10 { + margin-right: 2.5rem; +} + +.mb-10 { + margin-bottom: 2.5rem; +} + +.ml-10 { + margin-left: 2.5rem; +} + +.mt-12 { + margin-top: 3rem; +} + +.mr-12 { + margin-right: 3rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.ml-12 { + margin-left: 3rem; +} + +.mt-16 { + margin-top: 4rem; +} + +.mr-16 { + margin-right: 4rem; +} + +.mb-16 { + margin-bottom: 4rem; +} + +.ml-16 { + margin-left: 4rem; +} + +.mt-20 { + margin-top: 5rem; +} + +.mr-20 { + margin-right: 5rem; +} + +.mb-20 { + margin-bottom: 5rem; +} + +.ml-20 { + margin-left: 5rem; +} + +.mt-24 { + margin-top: 6rem; +} + +.mr-24 { + margin-right: 6rem; +} + +.mb-24 { + margin-bottom: 6rem; +} + +.ml-24 { + margin-left: 6rem; +} + +.mt-32 { + margin-top: 8rem; +} + +.mr-32 { + margin-right: 8rem; +} + +.mb-32 { + margin-bottom: 8rem; +} + +.ml-32 { + margin-left: 8rem; +} + +.mt-40 { + margin-top: 10rem; +} + +.mr-40 { + margin-right: 10rem; +} + +.mb-40 { + margin-bottom: 10rem; +} + +.ml-40 { + margin-left: 10rem; +} + +.mt-48 { + margin-top: 12rem; +} + +.mr-48 { + margin-right: 12rem; +} + +.mb-48 { + margin-bottom: 12rem; +} + +.ml-48 { + margin-left: 12rem; +} + +.mt-56 { + margin-top: 14rem; +} + +.mr-56 { + margin-right: 14rem; +} + +.mb-56 { + margin-bottom: 14rem; +} + +.ml-56 { + margin-left: 14rem; +} + +.mt-64 { + margin-top: 16rem; +} + +.mr-64 { + margin-right: 16rem; +} + +.mb-64 { + margin-bottom: 16rem; +} + +.ml-64 { + margin-left: 16rem; +} + +.mt-auto { + margin-top: auto; +} + +.mr-auto { + margin-right: auto; +} + +.mb-auto { + margin-bottom: auto; +} + +.ml-auto { + margin-left: auto; +} + +.mt-px { + margin-top: 1px; +} + +.mr-px { + margin-right: 1px; +} + +.mb-px { + margin-bottom: 1px; +} + +.ml-px { + margin-left: 1px; +} + +.-mt-1 { + margin-top: -0.25rem; +} + +.-mr-1 { + margin-right: -0.25rem; +} + +.-mb-1 { + margin-bottom: -0.25rem; +} + +.-ml-1 { + margin-left: -0.25rem; +} + +.-mt-2 { + margin-top: -0.5rem; +} + +.-mr-2 { + margin-right: -0.5rem; +} + +.-mb-2 { + margin-bottom: -0.5rem; +} + +.-ml-2 { + margin-left: -0.5rem; +} + +.-mt-3 { + margin-top: -0.75rem; +} + +.-mr-3 { + margin-right: -0.75rem; +} + +.-mb-3 { + margin-bottom: -0.75rem; +} + +.-ml-3 { + margin-left: -0.75rem; +} + +.-mt-4 { + margin-top: -1rem; +} + +.-mr-4 { + margin-right: -1rem; +} + +.-mb-4 { + margin-bottom: -1rem; +} + +.-ml-4 { + margin-left: -1rem; +} + +.-mt-5 { + margin-top: -1.25rem; +} + +.-mr-5 { + margin-right: -1.25rem; +} + +.-mb-5 { + margin-bottom: -1.25rem; +} + +.-ml-5 { + margin-left: -1.25rem; +} + +.-mt-6 { + margin-top: -1.5rem; +} + +.-mr-6 { + margin-right: -1.5rem; +} + +.-mb-6 { + margin-bottom: -1.5rem; +} + +.-ml-6 { + margin-left: -1.5rem; +} + +.-mt-8 { + margin-top: -2rem; +} + +.-mr-8 { + margin-right: -2rem; +} + +.-mb-8 { + margin-bottom: -2rem; +} + +.-ml-8 { + margin-left: -2rem; +} + +.-mt-10 { + margin-top: -2.5rem; +} + +.-mr-10 { + margin-right: -2.5rem; +} + +.-mb-10 { + margin-bottom: -2.5rem; +} + +.-ml-10 { + margin-left: -2.5rem; +} + +.-mt-12 { + margin-top: -3rem; +} + +.-mr-12 { + margin-right: -3rem; +} + +.-mb-12 { + margin-bottom: -3rem; +} + +.-ml-12 { + margin-left: -3rem; +} + +.-mt-16 { + margin-top: -4rem; +} + +.-mr-16 { + margin-right: -4rem; +} + +.-mb-16 { + margin-bottom: -4rem; +} + +.-ml-16 { + margin-left: -4rem; +} + +.-mt-20 { + margin-top: -5rem; +} + +.-mr-20 { + margin-right: -5rem; +} + +.-mb-20 { + margin-bottom: -5rem; +} + +.-ml-20 { + margin-left: -5rem; +} + +.-mt-24 { + margin-top: -6rem; +} + +.-mr-24 { + margin-right: -6rem; +} + +.-mb-24 { + margin-bottom: -6rem; +} + +.-ml-24 { + margin-left: -6rem; +} + +.-mt-32 { + margin-top: -8rem; +} + +.-mr-32 { + margin-right: -8rem; +} + +.-mb-32 { + margin-bottom: -8rem; +} + +.-ml-32 { + margin-left: -8rem; +} + +.-mt-40 { + margin-top: -10rem; +} + +.-mr-40 { + margin-right: -10rem; +} + +.-mb-40 { + margin-bottom: -10rem; +} + +.-ml-40 { + margin-left: -10rem; +} + +.-mt-48 { + margin-top: -12rem; +} + +.-mr-48 { + margin-right: -12rem; +} + +.-mb-48 { + margin-bottom: -12rem; +} + +.-ml-48 { + margin-left: -12rem; +} + +.-mt-56 { + margin-top: -14rem; +} + +.-mr-56 { + margin-right: -14rem; +} + +.-mb-56 { + margin-bottom: -14rem; +} + +.-ml-56 { + margin-left: -14rem; +} + +.-mt-64 { + margin-top: -16rem; +} + +.-mr-64 { + margin-right: -16rem; +} + +.-mb-64 { + margin-bottom: -16rem; +} + +.-ml-64 { + margin-left: -16rem; +} + +.-mt-px { + margin-top: -1px; +} + +.-mr-px { + margin-right: -1px; +} + +.-mb-px { + margin-bottom: -1px; +} + +.-ml-px { + margin-left: -1px; +} + +.max-h-full { + max-height: 100%; +} + +.max-h-screen { + max-height: 100vh; +} + +.max-w-none { + max-width: none; +} + +.max-w-xs { + max-width: 20rem; +} + +.max-w-sm { + max-width: 24rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-xl { + max-width: 36rem; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.max-w-6xl { + max-width: 72rem; +} + +.max-w-full { + max-width: 100%; +} + +.max-w-screen-sm { + max-width: 640px; +} + +.max-w-screen-md { + max-width: 768px; +} + +.max-w-screen-lg { + max-width: 1024px; +} + +.max-w-screen-xl { + max-width: 1280px; +} + +.min-h-0 { + min-height: 0; +} + +.min-h-full { + min-height: 100%; +} + +.min-h-screen { + min-height: 100vh; +} + +.min-w-0 { + min-width: 0; +} + +.min-w-full { + min-width: 100%; +} + +.object-contain { + -o-object-fit: contain; + object-fit: contain; +} + +.object-cover { + -o-object-fit: cover; + object-fit: cover; +} + +.object-fill { + -o-object-fit: fill; + object-fit: fill; +} + +.object-none { + -o-object-fit: none; + object-fit: none; +} + +.object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; +} + +.object-bottom { + -o-object-position: bottom; + object-position: bottom; +} + +.object-center { + -o-object-position: center; + object-position: center; +} + +.object-left { + -o-object-position: left; + object-position: left; +} + +.object-left-bottom { + -o-object-position: left bottom; + object-position: left bottom; +} + +.object-left-top { + -o-object-position: left top; + object-position: left top; +} + +.object-right { + -o-object-position: right; + object-position: right; +} + +.object-right-bottom { + -o-object-position: right bottom; + object-position: right bottom; +} + +.object-right-top { + -o-object-position: right top; + object-position: right top; +} + +.object-top { + -o-object-position: top; + object-position: top; +} + +.opacity-0 { + opacity: 0; +} + +.opacity-25 { + opacity: 0.25; +} + +.opacity-50 { + opacity: 0.5; +} + +.opacity-75 { + opacity: 0.75; +} + +.opacity-100 { + opacity: 1; +} + +.hover\:opacity-0:hover { + opacity: 0; +} + +.hover\:opacity-25:hover { + opacity: 0.25; +} + +.hover\:opacity-50:hover { + opacity: 0.5; +} + +.hover\:opacity-75:hover { + opacity: 0.75; +} + +.hover\:opacity-100:hover { + opacity: 1; +} + +.focus\:opacity-0:focus { + opacity: 0; +} + +.focus\:opacity-25:focus { + opacity: 0.25; +} + +.focus\:opacity-50:focus { + opacity: 0.5; +} + +.focus\:opacity-75:focus { + opacity: 0.75; +} + +.focus\:opacity-100:focus { + opacity: 1; +} + +.outline-none { + outline: 0; +} + +.focus\:outline-none:focus { + outline: 0; +} + +.overflow-auto { + overflow: auto; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-visible { + overflow: visible; +} + +.overflow-scroll { + overflow: scroll; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.overflow-x-hidden { + overflow-x: hidden; +} + +.overflow-y-hidden { + overflow-y: hidden; +} + +.overflow-x-visible { + overflow-x: visible; +} + +.overflow-y-visible { + overflow-y: visible; +} + +.overflow-x-scroll { + overflow-x: scroll; +} + +.overflow-y-scroll { + overflow-y: scroll; +} + +.scrolling-touch { + -webkit-overflow-scrolling: touch; +} + +.scrolling-auto { + -webkit-overflow-scrolling: auto; +} + +.overscroll-auto { + -ms-scroll-chaining: chained; + overscroll-behavior: auto; +} + +.overscroll-contain { + -ms-scroll-chaining: none; + overscroll-behavior: contain; +} + +.overscroll-none { + -ms-scroll-chaining: none; + overscroll-behavior: none; +} + +.overscroll-y-auto { + overscroll-behavior-y: auto; +} + +.overscroll-y-contain { + overscroll-behavior-y: contain; +} + +.overscroll-y-none { + overscroll-behavior-y: none; +} + +.overscroll-x-auto { + overscroll-behavior-x: auto; +} + +.overscroll-x-contain { + overscroll-behavior-x: contain; +} + +.overscroll-x-none { + overscroll-behavior-x: none; +} + +.p-0 { + padding: 0; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.p-10 { + padding: 2.5rem; +} + +.p-12 { + padding: 3rem; +} + +.p-16 { + padding: 4rem; +} + +.p-20 { + padding: 5rem; +} + +.p-24 { + padding: 6rem; +} + +.p-32 { + padding: 8rem; +} + +.p-40 { + padding: 10rem; +} + +.p-48 { + padding: 12rem; +} + +.p-56 { + padding: 14rem; +} + +.p-64 { + padding: 16rem; +} + +.p-px { + padding: 1px; +} + +.py-0 { + padding-top: 0; + padding-bottom: 0; +} + +.px-0 { + padding-left: 0; + padding-right: 0; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; +} + +.px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; +} + +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + +.px-12 { + padding-left: 3rem; + padding-right: 3rem; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.px-16 { + padding-left: 4rem; + padding-right: 4rem; +} + +.py-20 { + padding-top: 5rem; + padding-bottom: 5rem; +} + +.px-20 { + padding-left: 5rem; + padding-right: 5rem; +} + +.py-24 { + padding-top: 6rem; + padding-bottom: 6rem; +} + +.px-24 { + padding-left: 6rem; + padding-right: 6rem; +} + +.py-32 { + padding-top: 8rem; + padding-bottom: 8rem; +} + +.px-32 { + padding-left: 8rem; + padding-right: 8rem; +} + +.py-40 { + padding-top: 10rem; + padding-bottom: 10rem; +} + +.px-40 { + padding-left: 10rem; + padding-right: 10rem; +} + +.py-48 { + padding-top: 12rem; + padding-bottom: 12rem; +} + +.px-48 { + padding-left: 12rem; + padding-right: 12rem; +} + +.py-56 { + padding-top: 14rem; + padding-bottom: 14rem; +} + +.px-56 { + padding-left: 14rem; + padding-right: 14rem; +} + +.py-64 { + padding-top: 16rem; + padding-bottom: 16rem; +} + +.px-64 { + padding-left: 16rem; + padding-right: 16rem; +} + +.py-px { + padding-top: 1px; + padding-bottom: 1px; +} + +.px-px { + padding-left: 1px; + padding-right: 1px; +} + +.pt-0 { + padding-top: 0; +} + +.pr-0 { + padding-right: 0; +} + +.pb-0 { + padding-bottom: 0; +} + +.pl-0 { + padding-left: 0; +} + +.pt-1 { + padding-top: 0.25rem; +} + +.pr-1 { + padding-right: 0.25rem; +} + +.pb-1 { + padding-bottom: 0.25rem; +} + +.pl-1 { + padding-left: 0.25rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pr-2 { + padding-right: 0.5rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.pl-2 { + padding-left: 0.5rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + +.pr-3 { + padding-right: 0.75rem; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pl-4 { + padding-left: 1rem; +} + +.pt-5 { + padding-top: 1.25rem; +} + +.pr-5 { + padding-right: 1.25rem; +} + +.pb-5 { + padding-bottom: 1.25rem; +} + +.pl-5 { + padding-left: 1.25rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.pl-6 { + padding-left: 1.5rem; +} + +.pt-8 { + padding-top: 2rem; +} + +.pr-8 { + padding-right: 2rem; +} + +.pb-8 { + padding-bottom: 2rem; +} + +.pl-8 { + padding-left: 2rem; +} + +.pt-10 { + padding-top: 2.5rem; +} + +.pr-10 { + padding-right: 2.5rem; +} + +.pb-10 { + padding-bottom: 2.5rem; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pt-12 { + padding-top: 3rem; +} + +.pr-12 { + padding-right: 3rem; +} + +.pb-12 { + padding-bottom: 3rem; +} + +.pl-12 { + padding-left: 3rem; +} + +.pt-16 { + padding-top: 4rem; +} + +.pr-16 { + padding-right: 4rem; +} + +.pb-16 { + padding-bottom: 4rem; +} + +.pl-16 { + padding-left: 4rem; +} + +.pt-20 { + padding-top: 5rem; +} + +.pr-20 { + padding-right: 5rem; +} + +.pb-20 { + padding-bottom: 5rem; +} + +.pl-20 { + padding-left: 5rem; +} + +.pt-24 { + padding-top: 6rem; +} + +.pr-24 { + padding-right: 6rem; +} + +.pb-24 { + padding-bottom: 6rem; +} + +.pl-24 { + padding-left: 6rem; +} + +.pt-32 { + padding-top: 8rem; +} + +.pr-32 { + padding-right: 8rem; +} + +.pb-32 { + padding-bottom: 8rem; +} + +.pl-32 { + padding-left: 8rem; +} + +.pt-40 { + padding-top: 10rem; +} + +.pr-40 { + padding-right: 10rem; +} + +.pb-40 { + padding-bottom: 10rem; +} + +.pl-40 { + padding-left: 10rem; +} + +.pt-48 { + padding-top: 12rem; +} + +.pr-48 { + padding-right: 12rem; +} + +.pb-48 { + padding-bottom: 12rem; +} + +.pl-48 { + padding-left: 12rem; +} + +.pt-56 { + padding-top: 14rem; +} + +.pr-56 { + padding-right: 14rem; +} + +.pb-56 { + padding-bottom: 14rem; +} + +.pl-56 { + padding-left: 14rem; +} + +.pt-64 { + padding-top: 16rem; +} + +.pr-64 { + padding-right: 16rem; +} + +.pb-64 { + padding-bottom: 16rem; +} + +.pl-64 { + padding-left: 16rem; +} + +.pt-px { + padding-top: 1px; +} + +.pr-px { + padding-right: 1px; +} + +.pb-px { + padding-bottom: 1px; +} + +.pl-px { + padding-left: 1px; +} + +.placeholder-transparent::-moz-placeholder { + color: transparent; +} + +.placeholder-transparent:-ms-input-placeholder { + color: transparent; +} + +.placeholder-transparent::placeholder { + color: transparent; +} + +.placeholder-current::-moz-placeholder { + color: currentColor; +} + +.placeholder-current:-ms-input-placeholder { + color: currentColor; +} + +.placeholder-current::placeholder { + color: currentColor; +} + +.placeholder-black::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.placeholder-black:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.placeholder-black::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.placeholder-white::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.placeholder-white:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.placeholder-white::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.placeholder-gray-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.placeholder-gray-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.placeholder-gray-100::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.placeholder-gray-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.placeholder-gray-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.placeholder-gray-200::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.placeholder-gray-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.placeholder-gray-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.placeholder-gray-300::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.placeholder-gray-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.placeholder-gray-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.placeholder-gray-500::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.placeholder-gray-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.placeholder-gray-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.placeholder-gray-600::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.placeholder-gray-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.placeholder-gray-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.placeholder-gray-700::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.placeholder-gray-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.placeholder-gray-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.placeholder-gray-800::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.placeholder-gray-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.placeholder-gray-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.placeholder-gray-900::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.placeholder-red-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.placeholder-red-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.placeholder-red-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.placeholder-red-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.placeholder-red-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.placeholder-red-200::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.placeholder-red-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.placeholder-red-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.placeholder-red-300::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.placeholder-red-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.placeholder-red-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.placeholder-red-400::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.placeholder-red-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.placeholder-red-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.placeholder-red-500::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.placeholder-red-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.placeholder-red-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.placeholder-red-600::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.placeholder-red-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.placeholder-red-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.placeholder-red-700::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.placeholder-red-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.placeholder-red-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.placeholder-red-800::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.placeholder-red-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.placeholder-red-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.placeholder-red-900::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.placeholder-orange-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.placeholder-orange-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.placeholder-orange-100::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.placeholder-orange-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.placeholder-orange-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.placeholder-orange-200::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.placeholder-orange-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.placeholder-orange-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.placeholder-orange-300::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.placeholder-orange-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.placeholder-orange-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.placeholder-orange-400::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.placeholder-orange-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.placeholder-orange-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.placeholder-orange-500::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.placeholder-orange-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.placeholder-orange-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.placeholder-orange-600::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.placeholder-orange-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-700::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-800::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.placeholder-orange-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.placeholder-orange-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.placeholder-orange-900::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.placeholder-yellow-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.placeholder-yellow-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.placeholder-yellow-100::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.placeholder-yellow-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.placeholder-yellow-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.placeholder-yellow-200::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.placeholder-yellow-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.placeholder-yellow-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.placeholder-yellow-300::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.placeholder-yellow-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.placeholder-yellow-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.placeholder-yellow-400::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.placeholder-yellow-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.placeholder-yellow-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.placeholder-yellow-500::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.placeholder-yellow-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.placeholder-yellow-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.placeholder-yellow-600::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.placeholder-yellow-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.placeholder-yellow-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.placeholder-yellow-700::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.placeholder-yellow-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.placeholder-yellow-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.placeholder-yellow-800::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.placeholder-yellow-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.placeholder-yellow-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.placeholder-yellow-900::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.placeholder-green-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.placeholder-green-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.placeholder-green-100::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.placeholder-green-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.placeholder-green-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.placeholder-green-200::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.placeholder-green-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.placeholder-green-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.placeholder-green-300::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.placeholder-green-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.placeholder-green-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.placeholder-green-400::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.placeholder-green-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.placeholder-green-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.placeholder-green-500::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.placeholder-green-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.placeholder-green-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.placeholder-green-600::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.placeholder-green-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.placeholder-green-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.placeholder-green-700::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.placeholder-green-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.placeholder-green-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.placeholder-green-800::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.placeholder-green-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.placeholder-green-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.placeholder-green-900::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.placeholder-teal-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.placeholder-teal-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.placeholder-teal-100::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.placeholder-teal-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.placeholder-teal-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.placeholder-teal-200::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.placeholder-teal-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.placeholder-teal-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.placeholder-teal-300::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.placeholder-teal-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.placeholder-teal-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.placeholder-teal-400::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.placeholder-teal-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.placeholder-teal-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.placeholder-teal-500::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.placeholder-teal-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.placeholder-teal-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.placeholder-teal-600::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.placeholder-teal-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.placeholder-teal-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.placeholder-teal-700::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.placeholder-teal-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.placeholder-teal-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.placeholder-teal-800::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.placeholder-teal-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.placeholder-teal-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.placeholder-teal-900::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.placeholder-blue-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.placeholder-blue-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.placeholder-blue-100::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.placeholder-blue-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.placeholder-blue-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.placeholder-blue-200::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.placeholder-blue-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.placeholder-blue-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.placeholder-blue-300::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.placeholder-blue-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.placeholder-blue-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.placeholder-blue-400::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.placeholder-blue-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.placeholder-blue-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.placeholder-blue-500::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.placeholder-blue-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.placeholder-blue-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.placeholder-blue-600::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.placeholder-blue-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.placeholder-blue-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.placeholder-blue-700::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.placeholder-blue-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.placeholder-blue-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.placeholder-blue-800::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.placeholder-blue-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.placeholder-blue-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.placeholder-blue-900::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.placeholder-indigo-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.placeholder-indigo-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.placeholder-indigo-100::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.placeholder-indigo-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.placeholder-indigo-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.placeholder-indigo-200::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.placeholder-indigo-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.placeholder-indigo-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.placeholder-indigo-300::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.placeholder-indigo-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.placeholder-indigo-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.placeholder-indigo-400::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.placeholder-indigo-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.placeholder-indigo-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.placeholder-indigo-500::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.placeholder-indigo-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.placeholder-indigo-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.placeholder-indigo-600::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.placeholder-indigo-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.placeholder-indigo-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.placeholder-indigo-700::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.placeholder-indigo-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.placeholder-indigo-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.placeholder-indigo-800::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.placeholder-indigo-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.placeholder-indigo-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.placeholder-indigo-900::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.placeholder-purple-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.placeholder-purple-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.placeholder-purple-100::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.placeholder-purple-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.placeholder-purple-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.placeholder-purple-200::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.placeholder-purple-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.placeholder-purple-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.placeholder-purple-300::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.placeholder-purple-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.placeholder-purple-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.placeholder-purple-400::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.placeholder-purple-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.placeholder-purple-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.placeholder-purple-500::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.placeholder-purple-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.placeholder-purple-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.placeholder-purple-600::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.placeholder-purple-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.placeholder-purple-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.placeholder-purple-700::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.placeholder-purple-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.placeholder-purple-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.placeholder-purple-800::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.placeholder-purple-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.placeholder-purple-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.placeholder-purple-900::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.placeholder-pink-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.placeholder-pink-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.placeholder-pink-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.placeholder-pink-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.placeholder-pink-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.placeholder-pink-200::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.placeholder-pink-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.placeholder-pink-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.placeholder-pink-300::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.placeholder-pink-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.placeholder-pink-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.placeholder-pink-400::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.placeholder-pink-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.placeholder-pink-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.placeholder-pink-500::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.placeholder-pink-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.placeholder-pink-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.placeholder-pink-600::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.placeholder-pink-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.placeholder-pink-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.placeholder-pink-700::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.placeholder-pink-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.placeholder-pink-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.placeholder-pink-800::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.placeholder-pink-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.placeholder-pink-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.placeholder-pink-900::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.focus\:placeholder-transparent:focus::-moz-placeholder { + color: transparent; +} + +.focus\:placeholder-transparent:focus:-ms-input-placeholder { + color: transparent; +} + +.focus\:placeholder-transparent:focus::placeholder { + color: transparent; +} + +.focus\:placeholder-current:focus::-moz-placeholder { + color: currentColor; +} + +.focus\:placeholder-current:focus:-ms-input-placeholder { + color: currentColor; +} + +.focus\:placeholder-current:focus::placeholder { + color: currentColor; +} + +.focus\:placeholder-black:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.focus\:placeholder-black:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.focus\:placeholder-black:focus::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); +} + +.focus\:placeholder-white:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-white:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-white:focus::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-200:focus::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-300:focus::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-400:focus::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-500:focus::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-600:focus::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-gray-900:focus::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-300:focus::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-400:focus::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-500:focus::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-600:focus::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.focus\:placeholder-red-900:focus::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-200:focus::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-600:focus::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.focus\:placeholder-orange-900:focus::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-300:focus::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-800:focus::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.focus\:placeholder-yellow-900:focus::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-300:focus::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-400:focus::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-500:focus::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-600:focus::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-800:focus::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.focus\:placeholder-green-900:focus::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-100:focus::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-200:focus::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-300:focus::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-400:focus::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-500:focus::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-600:focus::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-800:focus::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.focus\:placeholder-teal-900:focus::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-200:focus::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-300:focus::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-400:focus::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-500:focus::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-600:focus::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-blue-900:focus::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-300:focus::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-400:focus::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-500:focus::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-600:focus::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-800:focus::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.focus\:placeholder-indigo-900:focus::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-100:focus::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-200:focus::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-300:focus::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-400:focus::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-500:focus::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-600:focus::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-700:focus::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-800:focus::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.focus\:placeholder-purple-900:focus::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-800:focus::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.focus\:placeholder-pink-900:focus::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); +} + +.placeholder-opacity-0::-moz-placeholder { + --placeholder-opacity: 0; +} + +.placeholder-opacity-0:-ms-input-placeholder { + --placeholder-opacity: 0; +} + +.placeholder-opacity-0::placeholder { + --placeholder-opacity: 0; +} + +.placeholder-opacity-25::-moz-placeholder { + --placeholder-opacity: 0.25; +} + +.placeholder-opacity-25:-ms-input-placeholder { + --placeholder-opacity: 0.25; +} + +.placeholder-opacity-25::placeholder { + --placeholder-opacity: 0.25; +} + +.placeholder-opacity-50::-moz-placeholder { + --placeholder-opacity: 0.5; +} + +.placeholder-opacity-50:-ms-input-placeholder { + --placeholder-opacity: 0.5; +} + +.placeholder-opacity-50::placeholder { + --placeholder-opacity: 0.5; +} + +.placeholder-opacity-75::-moz-placeholder { + --placeholder-opacity: 0.75; +} + +.placeholder-opacity-75:-ms-input-placeholder { + --placeholder-opacity: 0.75; +} + +.placeholder-opacity-75::placeholder { + --placeholder-opacity: 0.75; +} + +.placeholder-opacity-100::-moz-placeholder { + --placeholder-opacity: 1; +} + +.placeholder-opacity-100:-ms-input-placeholder { + --placeholder-opacity: 1; +} + +.placeholder-opacity-100::placeholder { + --placeholder-opacity: 1; +} + +.focus\:placeholder-opacity-0:focus::-moz-placeholder { + --placeholder-opacity: 0; +} + +.focus\:placeholder-opacity-0:focus:-ms-input-placeholder { + --placeholder-opacity: 0; +} + +.focus\:placeholder-opacity-0:focus::placeholder { + --placeholder-opacity: 0; +} + +.focus\:placeholder-opacity-25:focus::-moz-placeholder { + --placeholder-opacity: 0.25; +} + +.focus\:placeholder-opacity-25:focus:-ms-input-placeholder { + --placeholder-opacity: 0.25; +} + +.focus\:placeholder-opacity-25:focus::placeholder { + --placeholder-opacity: 0.25; +} + +.focus\:placeholder-opacity-50:focus::-moz-placeholder { + --placeholder-opacity: 0.5; +} + +.focus\:placeholder-opacity-50:focus:-ms-input-placeholder { + --placeholder-opacity: 0.5; +} + +.focus\:placeholder-opacity-50:focus::placeholder { + --placeholder-opacity: 0.5; +} + +.focus\:placeholder-opacity-75:focus::-moz-placeholder { + --placeholder-opacity: 0.75; +} + +.focus\:placeholder-opacity-75:focus:-ms-input-placeholder { + --placeholder-opacity: 0.75; +} + +.focus\:placeholder-opacity-75:focus::placeholder { + --placeholder-opacity: 0.75; +} + +.focus\:placeholder-opacity-100:focus::-moz-placeholder { + --placeholder-opacity: 1; +} + +.focus\:placeholder-opacity-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; +} + +.focus\:placeholder-opacity-100:focus::placeholder { + --placeholder-opacity: 1; +} + +.pointer-events-none { + pointer-events: none; +} + +.pointer-events-auto { + pointer-events: auto; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: -webkit-sticky; + position: sticky; +} + +.inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.inset-auto { + top: auto; + right: auto; + bottom: auto; + left: auto; +} + +.inset-y-0 { + top: 0; + bottom: 0; +} + +.inset-x-0 { + right: 0; + left: 0; +} + +.inset-y-auto { + top: auto; + bottom: auto; +} + +.inset-x-auto { + right: auto; + left: auto; +} + +.top-0 { + top: 0; +} + +.right-0 { + right: 0; +} + +.bottom-0 { + bottom: 0; +} + +.left-0 { + left: 0; +} + +.top-auto { + top: auto; +} + +.right-auto { + right: auto; +} + +.bottom-auto { + bottom: auto; +} + +.left-auto { + left: auto; +} + +.resize-none { + resize: none; +} + +.resize-y { + resize: vertical; +} + +.resize-x { + resize: horizontal; +} + +.resize { + resize: both; +} + +.shadow-xs { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); +} + +.shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +} + +.shadow-inner { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-outline { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.shadow-none { + box-shadow: none; +} + +.hover\:shadow-xs:hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); +} + +.hover\:shadow-sm:hover { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.hover\:shadow:hover { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.hover\:shadow-xl:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.hover\:shadow-2xl:hover { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +} + +.hover\:shadow-inner:hover { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); +} + +.hover\:shadow-outline:hover { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.hover\:shadow-none:hover { + box-shadow: none; +} + +.focus\:shadow-xs:focus { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); +} + +.focus\:shadow-sm:focus { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.focus\:shadow:focus { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.focus\:shadow-md:focus { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.focus\:shadow-lg:focus { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.focus\:shadow-xl:focus { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.focus\:shadow-2xl:focus { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +} + +.focus\:shadow-inner:focus { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); +} + +.focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.focus\:shadow-none:focus { + box-shadow: none; +} + +.fill-current { + fill: currentColor; +} + +.stroke-current { + stroke: currentColor; +} + +.stroke-0 { + stroke-width: 0; +} + +.stroke-1 { + stroke-width: 1; +} + +.stroke-2 { + stroke-width: 2; +} + +.table-auto { + table-layout: auto; +} + +.table-fixed { + table-layout: fixed; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-justify { + text-align: justify; +} + +.text-transparent { + color: transparent; +} + +.text-current { + color: currentColor; +} + +.text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); +} + +.text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); +} + +.text-gray-100 { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); +} + +.text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); +} + +.text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); +} + +.text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); +} + +.text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); +} + +.text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); +} + +.text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); +} + +.text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); +} + +.text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); +} + +.text-red-100 { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); +} + +.text-red-200 { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); +} + +.text-red-300 { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); +} + +.text-red-400 { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); +} + +.text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); +} + +.text-red-600 { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); +} + +.text-red-700 { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); +} + +.text-red-800 { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); +} + +.text-red-900 { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); +} + +.text-orange-100 { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); +} + +.text-orange-200 { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); +} + +.text-orange-300 { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); +} + +.text-orange-400 { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); +} + +.text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); +} + +.text-orange-600 { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); +} + +.text-orange-700 { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); +} + +.text-orange-800 { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); +} + +.text-orange-900 { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); +} + +.text-yellow-100 { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); +} + +.text-yellow-200 { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); +} + +.text-yellow-300 { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); +} + +.text-yellow-400 { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); +} + +.text-yellow-500 { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); +} + +.text-yellow-600 { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); +} + +.text-yellow-700 { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); +} + +.text-yellow-800 { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); +} + +.text-yellow-900 { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); +} + +.text-green-100 { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); +} + +.text-green-200 { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); +} + +.text-green-300 { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); +} + +.text-green-400 { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); +} + +.text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); +} + +.text-green-600 { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); +} + +.text-green-700 { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); +} + +.text-green-800 { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); +} + +.text-green-900 { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); +} + +.text-teal-100 { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); +} + +.text-teal-200 { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); +} + +.text-teal-300 { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); +} + +.text-teal-400 { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); +} + +.text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); +} + +.text-teal-600 { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); +} + +.text-teal-700 { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); +} + +.text-teal-800 { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); +} + +.text-teal-900 { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); +} + +.text-blue-100 { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); +} + +.text-blue-200 { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); +} + +.text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); +} + +.text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); +} + +.text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); +} + +.text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); +} + +.text-blue-700 { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); +} + +.text-blue-800 { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); +} + +.text-blue-900 { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); +} + +.text-indigo-100 { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); +} + +.text-indigo-200 { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); +} + +.text-indigo-300 { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); +} + +.text-indigo-400 { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); +} + +.text-indigo-500 { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); +} + +.text-indigo-600 { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); +} + +.text-indigo-700 { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); +} + +.text-indigo-800 { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); +} + +.text-indigo-900 { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); +} + +.text-purple-100 { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); +} + +.text-purple-200 { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); +} + +.text-purple-300 { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); +} + +.text-purple-400 { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); +} + +.text-purple-500 { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); +} + +.text-purple-600 { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); +} + +.text-purple-700 { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); +} + +.text-purple-800 { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); +} + +.text-purple-900 { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); +} + +.text-pink-100 { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); +} + +.text-pink-200 { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); +} + +.text-pink-300 { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); +} + +.text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); +} + +.text-pink-500 { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); +} + +.text-pink-600 { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); +} + +.text-pink-700 { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); +} + +.text-pink-800 { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); +} + +.text-pink-900 { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); +} + +.hover\:text-transparent:hover { + color: transparent; +} + +.hover\:text-current:hover { + color: currentColor; +} + +.hover\:text-black:hover { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); +} + +.hover\:text-white:hover { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); +} + +.hover\:text-gray-100:hover { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); +} + +.hover\:text-gray-200:hover { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); +} + +.hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); +} + +.hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); +} + +.hover\:text-gray-500:hover { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); +} + +.hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); +} + +.hover\:text-gray-700:hover { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); +} + +.hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); +} + +.hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); +} + +.hover\:text-red-100:hover { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); +} + +.hover\:text-red-200:hover { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); +} + +.hover\:text-red-300:hover { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); +} + +.hover\:text-red-400:hover { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); +} + +.hover\:text-red-500:hover { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); +} + +.hover\:text-red-600:hover { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); +} + +.hover\:text-red-700:hover { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); +} + +.hover\:text-red-800:hover { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); +} + +.hover\:text-red-900:hover { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); +} + +.hover\:text-orange-100:hover { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); +} + +.hover\:text-orange-200:hover { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); +} + +.hover\:text-orange-300:hover { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); +} + +.hover\:text-orange-400:hover { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); +} + +.hover\:text-orange-500:hover { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); +} + +.hover\:text-orange-600:hover { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); +} + +.hover\:text-orange-700:hover { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); +} + +.hover\:text-orange-800:hover { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); +} + +.hover\:text-orange-900:hover { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); +} + +.hover\:text-yellow-100:hover { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); +} + +.hover\:text-yellow-200:hover { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); +} + +.hover\:text-yellow-300:hover { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); +} + +.hover\:text-yellow-400:hover { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); +} + +.hover\:text-yellow-500:hover { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); +} + +.hover\:text-yellow-600:hover { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); +} + +.hover\:text-yellow-700:hover { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); +} + +.hover\:text-yellow-800:hover { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); +} + +.hover\:text-yellow-900:hover { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); +} + +.hover\:text-green-100:hover { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); +} + +.hover\:text-green-200:hover { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); +} + +.hover\:text-green-300:hover { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); +} + +.hover\:text-green-400:hover { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); +} + +.hover\:text-green-500:hover { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); +} + +.hover\:text-green-600:hover { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); +} + +.hover\:text-green-700:hover { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); +} + +.hover\:text-green-800:hover { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); +} + +.hover\:text-green-900:hover { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); +} + +.hover\:text-teal-100:hover { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); +} + +.hover\:text-teal-200:hover { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); +} + +.hover\:text-teal-300:hover { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); +} + +.hover\:text-teal-400:hover { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); +} + +.hover\:text-teal-500:hover { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); +} + +.hover\:text-teal-600:hover { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); +} + +.hover\:text-teal-700:hover { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); +} + +.hover\:text-teal-800:hover { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); +} + +.hover\:text-teal-900:hover { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); +} + +.hover\:text-blue-100:hover { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); +} + +.hover\:text-blue-200:hover { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); +} + +.hover\:text-blue-300:hover { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); +} + +.hover\:text-blue-400:hover { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); +} + +.hover\:text-blue-500:hover { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); +} + +.hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); +} + +.hover\:text-blue-700:hover { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); +} + +.hover\:text-blue-800:hover { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); +} + +.hover\:text-blue-900:hover { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); +} + +.hover\:text-indigo-100:hover { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); +} + +.hover\:text-indigo-200:hover { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); +} + +.hover\:text-indigo-300:hover { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); +} + +.hover\:text-indigo-400:hover { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); +} + +.hover\:text-indigo-500:hover { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); +} + +.hover\:text-indigo-600:hover { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); +} + +.hover\:text-indigo-700:hover { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); +} + +.hover\:text-indigo-800:hover { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); +} + +.hover\:text-indigo-900:hover { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); +} + +.hover\:text-purple-100:hover { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); +} + +.hover\:text-purple-200:hover { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); +} + +.hover\:text-purple-300:hover { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); +} + +.hover\:text-purple-400:hover { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); +} + +.hover\:text-purple-500:hover { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); +} + +.hover\:text-purple-600:hover { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); +} + +.hover\:text-purple-700:hover { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); +} + +.hover\:text-purple-800:hover { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); +} + +.hover\:text-purple-900:hover { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); +} + +.hover\:text-pink-100:hover { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); +} + +.hover\:text-pink-200:hover { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); +} + +.hover\:text-pink-300:hover { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); +} + +.hover\:text-pink-400:hover { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); +} + +.hover\:text-pink-500:hover { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); +} + +.hover\:text-pink-600:hover { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); +} + +.hover\:text-pink-700:hover { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); +} + +.hover\:text-pink-800:hover { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); +} + +.hover\:text-pink-900:hover { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); +} + +.focus\:text-transparent:focus { + color: transparent; +} + +.focus\:text-current:focus { + color: currentColor; +} + +.focus\:text-black:focus { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); +} + +.focus\:text-white:focus { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); +} + +.focus\:text-gray-100:focus { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); +} + +.focus\:text-gray-200:focus { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); +} + +.focus\:text-gray-300:focus { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); +} + +.focus\:text-gray-400:focus { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); +} + +.focus\:text-gray-500:focus { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); +} + +.focus\:text-gray-600:focus { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); +} + +.focus\:text-gray-700:focus { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); +} + +.focus\:text-gray-800:focus { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); +} + +.focus\:text-gray-900:focus { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); +} + +.focus\:text-red-100:focus { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); +} + +.focus\:text-red-200:focus { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); +} + +.focus\:text-red-300:focus { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); +} + +.focus\:text-red-400:focus { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); +} + +.focus\:text-red-500:focus { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); +} + +.focus\:text-red-600:focus { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); +} + +.focus\:text-red-700:focus { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); +} + +.focus\:text-red-800:focus { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); +} + +.focus\:text-red-900:focus { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); +} + +.focus\:text-orange-100:focus { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); +} + +.focus\:text-orange-200:focus { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); +} + +.focus\:text-orange-300:focus { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); +} + +.focus\:text-orange-400:focus { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); +} + +.focus\:text-orange-500:focus { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); +} + +.focus\:text-orange-600:focus { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); +} + +.focus\:text-orange-700:focus { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); +} + +.focus\:text-orange-800:focus { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); +} + +.focus\:text-orange-900:focus { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); +} + +.focus\:text-yellow-100:focus { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); +} + +.focus\:text-yellow-200:focus { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); +} + +.focus\:text-yellow-300:focus { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); +} + +.focus\:text-yellow-400:focus { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); +} + +.focus\:text-yellow-500:focus { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); +} + +.focus\:text-yellow-600:focus { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); +} + +.focus\:text-yellow-700:focus { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); +} + +.focus\:text-yellow-800:focus { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); +} + +.focus\:text-yellow-900:focus { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); +} + +.focus\:text-green-100:focus { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); +} + +.focus\:text-green-200:focus { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); +} + +.focus\:text-green-300:focus { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); +} + +.focus\:text-green-400:focus { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); +} + +.focus\:text-green-500:focus { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); +} + +.focus\:text-green-600:focus { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); +} + +.focus\:text-green-700:focus { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); +} + +.focus\:text-green-800:focus { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); +} + +.focus\:text-green-900:focus { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); +} + +.focus\:text-teal-100:focus { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); +} + +.focus\:text-teal-200:focus { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); +} + +.focus\:text-teal-300:focus { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); +} + +.focus\:text-teal-400:focus { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); +} + +.focus\:text-teal-500:focus { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); +} + +.focus\:text-teal-600:focus { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); +} + +.focus\:text-teal-700:focus { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); +} + +.focus\:text-teal-800:focus { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); +} + +.focus\:text-teal-900:focus { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); +} + +.focus\:text-blue-100:focus { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); +} + +.focus\:text-blue-200:focus { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); +} + +.focus\:text-blue-300:focus { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); +} + +.focus\:text-blue-400:focus { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); +} + +.focus\:text-blue-500:focus { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); +} + +.focus\:text-blue-600:focus { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); +} + +.focus\:text-blue-700:focus { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); +} + +.focus\:text-blue-800:focus { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); +} + +.focus\:text-blue-900:focus { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); +} + +.focus\:text-indigo-100:focus { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); +} + +.focus\:text-indigo-200:focus { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); +} + +.focus\:text-indigo-300:focus { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); +} + +.focus\:text-indigo-400:focus { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); +} + +.focus\:text-indigo-500:focus { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); +} + +.focus\:text-indigo-600:focus { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); +} + +.focus\:text-indigo-700:focus { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); +} + +.focus\:text-indigo-800:focus { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); +} + +.focus\:text-indigo-900:focus { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); +} + +.focus\:text-purple-100:focus { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); +} + +.focus\:text-purple-200:focus { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); +} + +.focus\:text-purple-300:focus { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); +} + +.focus\:text-purple-400:focus { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); +} + +.focus\:text-purple-500:focus { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); +} + +.focus\:text-purple-600:focus { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); +} + +.focus\:text-purple-700:focus { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); +} + +.focus\:text-purple-800:focus { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); +} + +.focus\:text-purple-900:focus { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); +} + +.focus\:text-pink-100:focus { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); +} + +.focus\:text-pink-200:focus { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); +} + +.focus\:text-pink-300:focus { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); +} + +.focus\:text-pink-400:focus { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); +} + +.focus\:text-pink-500:focus { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); +} + +.focus\:text-pink-600:focus { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); +} + +.focus\:text-pink-700:focus { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); +} + +.focus\:text-pink-800:focus { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); +} + +.focus\:text-pink-900:focus { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); +} + +.text-opacity-0 { + --text-opacity: 0; +} + +.text-opacity-25 { + --text-opacity: 0.25; +} + +.text-opacity-50 { + --text-opacity: 0.5; +} + +.text-opacity-75 { + --text-opacity: 0.75; +} + +.text-opacity-100 { + --text-opacity: 1; +} + +.hover\:text-opacity-0:hover { + --text-opacity: 0; +} + +.hover\:text-opacity-25:hover { + --text-opacity: 0.25; +} + +.hover\:text-opacity-50:hover { + --text-opacity: 0.5; +} + +.hover\:text-opacity-75:hover { + --text-opacity: 0.75; +} + +.hover\:text-opacity-100:hover { + --text-opacity: 1; +} + +.focus\:text-opacity-0:focus { + --text-opacity: 0; +} + +.focus\:text-opacity-25:focus { + --text-opacity: 0.25; +} + +.focus\:text-opacity-50:focus { + --text-opacity: 0.5; +} + +.focus\:text-opacity-75:focus { + --text-opacity: 0.75; +} + +.focus\:text-opacity-100:focus { + --text-opacity: 1; +} + +.italic { + font-style: italic; +} + +.not-italic { + font-style: normal; +} + +.uppercase { + text-transform: uppercase; +} + +.lowercase { + text-transform: lowercase; +} + +.capitalize { + text-transform: capitalize; +} + +.normal-case { + text-transform: none; +} + +.underline { + text-decoration: underline; +} + +.line-through { + text-decoration: line-through; +} + +.no-underline { + text-decoration: none; +} + +.hover\:underline:hover { + text-decoration: underline; +} + +.hover\:line-through:hover { + text-decoration: line-through; +} + +.hover\:no-underline:hover { + text-decoration: none; +} + +.focus\:underline:focus { + text-decoration: underline; +} + +.focus\:line-through:focus { + text-decoration: line-through; +} + +.focus\:no-underline:focus { + text-decoration: none; +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; +} + +.ordinal, .slashed-zero, .lining-nums, .oldstyle-nums, .proportional-nums, .tabular-nums, .diagonal-fractions, .stacked-fractions { + --font-variant-numeric-ordinal: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-slashed-zero: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-figure: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-spacing: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-fraction: var(--tailwind-empty,/*!*/ /*!*/); + font-variant-numeric: var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction); +} + +.normal-nums { + font-variant-numeric: normal; +} + +.ordinal { + --font-variant-numeric-ordinal: ordinal; +} + +.slashed-zero { + --font-variant-numeric-slashed-zero: slashed-zero; +} + +.lining-nums { + --font-variant-numeric-figure: lining-nums; +} + +.oldstyle-nums { + --font-variant-numeric-figure: oldstyle-nums; +} + +.proportional-nums { + --font-variant-numeric-spacing: proportional-nums; +} + +.tabular-nums { + --font-variant-numeric-spacing: tabular-nums; +} + +.diagonal-fractions { + --font-variant-numeric-fraction: diagonal-fractions; +} + +.stacked-fractions { + --font-variant-numeric-fraction: stacked-fractions; +} + +.tracking-tighter { + letter-spacing: -0.05em; +} + +.tracking-tight { + letter-spacing: -0.025em; +} + +.tracking-normal { + letter-spacing: 0; +} + +.tracking-wide { + letter-spacing: 0.025em; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.tracking-widest { + letter-spacing: 0.1em; +} + +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.select-all { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; +} + +.select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; +} + +.align-baseline { + vertical-align: baseline; +} + +.align-top { + vertical-align: top; +} + +.align-middle { + vertical-align: middle; +} + +.align-bottom { + vertical-align: bottom; +} + +.align-text-top { + vertical-align: text-top; +} + +.align-text-bottom { + vertical-align: text-bottom; +} + +.visible { + visibility: visible; +} + +.invisible { + visibility: hidden; +} + +.whitespace-normal { + white-space: normal; +} + +.whitespace-no-wrap { + white-space: nowrap; +} + +.whitespace-pre { + white-space: pre; +} + +.whitespace-pre-line { + white-space: pre-line; +} + +.whitespace-pre-wrap { + white-space: pre-wrap; +} + +.break-normal { + overflow-wrap: normal; + word-break: normal; +} + +.break-words { + overflow-wrap: break-word; +} + +.break-all { + word-break: break-all; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.w-0 { + width: 0; +} + +.w-1 { + width: 0.25rem; +} + +.w-2 { + width: 0.5rem; +} + +.w-3 { + width: 0.75rem; +} + +.w-4 { + width: 1rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-8 { + width: 2rem; +} + +.w-10 { + width: 2.5rem; +} + +.w-12 { + width: 3rem; +} + +.w-16 { + width: 4rem; +} + +.w-20 { + width: 5rem; +} + +.w-24 { + width: 6rem; +} + +.w-32 { + width: 8rem; +} + +.w-40 { + width: 10rem; +} + +.w-48 { + width: 12rem; +} + +.w-56 { + width: 14rem; +} + +.w-64 { + width: 16rem; +} + +.w-auto { + width: auto; +} + +.w-px { + width: 1px; +} + +.w-1\/2 { + width: 50%; +} + +.w-1\/3 { + width: 33.333333%; +} + +.w-2\/3 { + width: 66.666667%; +} + +.w-1\/4 { + width: 25%; +} + +.w-2\/4 { + width: 50%; +} + +.w-3\/4 { + width: 75%; +} + +.w-1\/5 { + width: 20%; +} + +.w-2\/5 { + width: 40%; +} + +.w-3\/5 { + width: 60%; +} + +.w-4\/5 { + width: 80%; +} + +.w-1\/6 { + width: 16.666667%; +} + +.w-2\/6 { + width: 33.333333%; +} + +.w-3\/6 { + width: 50%; +} + +.w-4\/6 { + width: 66.666667%; +} + +.w-5\/6 { + width: 83.333333%; +} + +.w-1\/12 { + width: 8.333333%; +} + +.w-2\/12 { + width: 16.666667%; +} + +.w-3\/12 { + width: 25%; +} + +.w-4\/12 { + width: 33.333333%; +} + +.w-5\/12 { + width: 41.666667%; +} + +.w-6\/12 { + width: 50%; +} + +.w-7\/12 { + width: 58.333333%; +} + +.w-8\/12 { + width: 66.666667%; +} + +.w-9\/12 { + width: 75%; +} + +.w-10\/12 { + width: 83.333333%; +} + +.w-11\/12 { + width: 91.666667%; +} + +.w-full { + width: 100%; +} + +.w-screen { + width: 100vw; +} + +.z-0 { + z-index: 0; +} + +.z-10 { + z-index: 10; +} + +.z-20 { + z-index: 20; +} + +.z-30 { + z-index: 30; +} + +.z-40 { + z-index: 40; +} + +.z-50 { + z-index: 50; +} + +.z-auto { + z-index: auto; +} + +.gap-0 { + grid-gap: 0; + gap: 0; +} + +.gap-1 { + grid-gap: 0.25rem; + gap: 0.25rem; +} + +.gap-2 { + grid-gap: 0.5rem; + gap: 0.5rem; +} + +.gap-3 { + grid-gap: 0.75rem; + gap: 0.75rem; +} + +.gap-4 { + grid-gap: 1rem; + gap: 1rem; +} + +.gap-5 { + grid-gap: 1.25rem; + gap: 1.25rem; +} + +.gap-6 { + grid-gap: 1.5rem; + gap: 1.5rem; +} + +.gap-8 { + grid-gap: 2rem; + gap: 2rem; +} + +.gap-10 { + grid-gap: 2.5rem; + gap: 2.5rem; +} + +.gap-12 { + grid-gap: 3rem; + gap: 3rem; +} + +.gap-16 { + grid-gap: 4rem; + gap: 4rem; +} + +.gap-20 { + grid-gap: 5rem; + gap: 5rem; +} + +.gap-24 { + grid-gap: 6rem; + gap: 6rem; +} + +.gap-32 { + grid-gap: 8rem; + gap: 8rem; +} + +.gap-40 { + grid-gap: 10rem; + gap: 10rem; +} + +.gap-48 { + grid-gap: 12rem; + gap: 12rem; +} + +.gap-56 { + grid-gap: 14rem; + gap: 14rem; +} + +.gap-64 { + grid-gap: 16rem; + gap: 16rem; +} + +.gap-px { + grid-gap: 1px; + gap: 1px; +} + +.col-gap-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; +} + +.col-gap-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; +} + +.col-gap-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + +.col-gap-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; +} + +.col-gap-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; +} + +.col-gap-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; +} + +.col-gap-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; +} + +.col-gap-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; +} + +.col-gap-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; +} + +.col-gap-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; +} + +.col-gap-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; +} + +.col-gap-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; +} + +.col-gap-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; +} + +.col-gap-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; +} + +.col-gap-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; +} + +.col-gap-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; +} + +.col-gap-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; +} + +.col-gap-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; +} + +.col-gap-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; +} + +.gap-x-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; +} + +.gap-x-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; +} + +.gap-x-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + +.gap-x-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; +} + +.gap-x-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; +} + +.gap-x-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; +} + +.gap-x-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; +} + +.gap-x-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; +} + +.gap-x-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; +} + +.gap-x-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; +} + +.gap-x-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; +} + +.gap-x-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; +} + +.gap-x-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; +} + +.gap-x-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; +} + +.gap-x-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; +} + +.gap-x-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; +} + +.gap-x-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; +} + +.gap-x-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; +} + +.gap-x-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; +} + +.row-gap-0 { + grid-row-gap: 0; + row-gap: 0; +} + +.row-gap-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; +} + +.row-gap-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; +} + +.row-gap-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; +} + +.row-gap-4 { + grid-row-gap: 1rem; + row-gap: 1rem; +} + +.row-gap-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; +} + +.row-gap-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; +} + +.row-gap-8 { + grid-row-gap: 2rem; + row-gap: 2rem; +} + +.row-gap-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; +} + +.row-gap-12 { + grid-row-gap: 3rem; + row-gap: 3rem; +} + +.row-gap-16 { + grid-row-gap: 4rem; + row-gap: 4rem; +} + +.row-gap-20 { + grid-row-gap: 5rem; + row-gap: 5rem; +} + +.row-gap-24 { + grid-row-gap: 6rem; + row-gap: 6rem; +} + +.row-gap-32 { + grid-row-gap: 8rem; + row-gap: 8rem; +} + +.row-gap-40 { + grid-row-gap: 10rem; + row-gap: 10rem; +} + +.row-gap-48 { + grid-row-gap: 12rem; + row-gap: 12rem; +} + +.row-gap-56 { + grid-row-gap: 14rem; + row-gap: 14rem; +} + +.row-gap-64 { + grid-row-gap: 16rem; + row-gap: 16rem; +} + +.row-gap-px { + grid-row-gap: 1px; + row-gap: 1px; +} + +.gap-y-0 { + grid-row-gap: 0; + row-gap: 0; +} + +.gap-y-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; +} + +.gap-y-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; +} + +.gap-y-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; +} + +.gap-y-4 { + grid-row-gap: 1rem; + row-gap: 1rem; +} + +.gap-y-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; +} + +.gap-y-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; +} + +.gap-y-8 { + grid-row-gap: 2rem; + row-gap: 2rem; +} + +.gap-y-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; +} + +.gap-y-12 { + grid-row-gap: 3rem; + row-gap: 3rem; +} + +.gap-y-16 { + grid-row-gap: 4rem; + row-gap: 4rem; +} + +.gap-y-20 { + grid-row-gap: 5rem; + row-gap: 5rem; +} + +.gap-y-24 { + grid-row-gap: 6rem; + row-gap: 6rem; +} + +.gap-y-32 { + grid-row-gap: 8rem; + row-gap: 8rem; +} + +.gap-y-40 { + grid-row-gap: 10rem; + row-gap: 10rem; +} + +.gap-y-48 { + grid-row-gap: 12rem; + row-gap: 12rem; +} + +.gap-y-56 { + grid-row-gap: 14rem; + row-gap: 14rem; +} + +.gap-y-64 { + grid-row-gap: 16rem; + row-gap: 16rem; +} + +.gap-y-px { + grid-row-gap: 1px; + row-gap: 1px; +} + +.grid-flow-row { + grid-auto-flow: row; +} + +.grid-flow-col { + grid-auto-flow: column; +} + +.grid-flow-row-dense { + grid-auto-flow: row dense; +} + +.grid-flow-col-dense { + grid-auto-flow: column dense; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); +} + +.grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.grid-cols-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); +} + +.grid-cols-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); +} + +.grid-cols-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); +} + +.grid-cols-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); +} + +.grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + +.grid-cols-none { + grid-template-columns: none; +} + +.col-auto { + grid-column: auto; +} + +.col-span-1 { + grid-column: span 1 / span 1; +} + +.col-span-2 { + grid-column: span 2 / span 2; +} + +.col-span-3 { + grid-column: span 3 / span 3; +} + +.col-span-4 { + grid-column: span 4 / span 4; +} + +.col-span-5 { + grid-column: span 5 / span 5; +} + +.col-span-6 { + grid-column: span 6 / span 6; +} + +.col-span-7 { + grid-column: span 7 / span 7; +} + +.col-span-8 { + grid-column: span 8 / span 8; +} + +.col-span-9 { + grid-column: span 9 / span 9; +} + +.col-span-10 { + grid-column: span 10 / span 10; +} + +.col-span-11 { + grid-column: span 11 / span 11; +} + +.col-span-12 { + grid-column: span 12 / span 12; +} + +.col-start-1 { + grid-column-start: 1; +} + +.col-start-2 { + grid-column-start: 2; +} + +.col-start-3 { + grid-column-start: 3; +} + +.col-start-4 { + grid-column-start: 4; +} + +.col-start-5 { + grid-column-start: 5; +} + +.col-start-6 { + grid-column-start: 6; +} + +.col-start-7 { + grid-column-start: 7; +} + +.col-start-8 { + grid-column-start: 8; +} + +.col-start-9 { + grid-column-start: 9; +} + +.col-start-10 { + grid-column-start: 10; +} + +.col-start-11 { + grid-column-start: 11; +} + +.col-start-12 { + grid-column-start: 12; +} + +.col-start-13 { + grid-column-start: 13; +} + +.col-start-auto { + grid-column-start: auto; +} + +.col-end-1 { + grid-column-end: 1; +} + +.col-end-2 { + grid-column-end: 2; +} + +.col-end-3 { + grid-column-end: 3; +} + +.col-end-4 { + grid-column-end: 4; +} + +.col-end-5 { + grid-column-end: 5; +} + +.col-end-6 { + grid-column-end: 6; +} + +.col-end-7 { + grid-column-end: 7; +} + +.col-end-8 { + grid-column-end: 8; +} + +.col-end-9 { + grid-column-end: 9; +} + +.col-end-10 { + grid-column-end: 10; +} + +.col-end-11 { + grid-column-end: 11; +} + +.col-end-12 { + grid-column-end: 12; +} + +.col-end-13 { + grid-column-end: 13; +} + +.col-end-auto { + grid-column-end: auto; +} + +.grid-rows-1 { + grid-template-rows: repeat(1, minmax(0, 1fr)); +} + +.grid-rows-2 { + grid-template-rows: repeat(2, minmax(0, 1fr)); +} + +.grid-rows-3 { + grid-template-rows: repeat(3, minmax(0, 1fr)); +} + +.grid-rows-4 { + grid-template-rows: repeat(4, minmax(0, 1fr)); +} + +.grid-rows-5 { + grid-template-rows: repeat(5, minmax(0, 1fr)); +} + +.grid-rows-6 { + grid-template-rows: repeat(6, minmax(0, 1fr)); +} + +.grid-rows-none { + grid-template-rows: none; +} + +.row-auto { + grid-row: auto; +} + +.row-span-1 { + grid-row: span 1 / span 1; +} + +.row-span-2 { + grid-row: span 2 / span 2; +} + +.row-span-3 { + grid-row: span 3 / span 3; +} + +.row-span-4 { + grid-row: span 4 / span 4; +} + +.row-span-5 { + grid-row: span 5 / span 5; +} + +.row-span-6 { + grid-row: span 6 / span 6; +} + +.row-start-1 { + grid-row-start: 1; +} + +.row-start-2 { + grid-row-start: 2; +} + +.row-start-3 { + grid-row-start: 3; +} + +.row-start-4 { + grid-row-start: 4; +} + +.row-start-5 { + grid-row-start: 5; +} + +.row-start-6 { + grid-row-start: 6; +} + +.row-start-7 { + grid-row-start: 7; +} + +.row-start-auto { + grid-row-start: auto; +} + +.row-end-1 { + grid-row-end: 1; +} + +.row-end-2 { + grid-row-end: 2; +} + +.row-end-3 { + grid-row-end: 3; +} + +.row-end-4 { + grid-row-end: 4; +} + +.row-end-5 { + grid-row-end: 5; +} + +.row-end-6 { + grid-row-end: 6; +} + +.row-end-7 { + grid-row-end: 7; +} + +.row-end-auto { + grid-row-end: auto; +} + +.transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); +} + +.transform-none { + transform: none; +} + +.origin-center { + transform-origin: center; +} + +.origin-top { + transform-origin: top; +} + +.origin-top-right { + transform-origin: top right; +} + +.origin-right { + transform-origin: right; +} + +.origin-bottom-right { + transform-origin: bottom right; +} + +.origin-bottom { + transform-origin: bottom; +} + +.origin-bottom-left { + transform-origin: bottom left; +} + +.origin-left { + transform-origin: left; +} + +.origin-top-left { + transform-origin: top left; +} + +.scale-0 { + --transform-scale-x: 0; + --transform-scale-y: 0; +} + +.scale-50 { + --transform-scale-x: .5; + --transform-scale-y: .5; +} + +.scale-75 { + --transform-scale-x: .75; + --transform-scale-y: .75; +} + +.scale-90 { + --transform-scale-x: .9; + --transform-scale-y: .9; +} + +.scale-95 { + --transform-scale-x: .95; + --transform-scale-y: .95; +} + +.scale-100 { + --transform-scale-x: 1; + --transform-scale-y: 1; +} + +.scale-105 { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; +} + +.scale-110 { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; +} + +.scale-125 { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; +} + +.scale-150 { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; +} + +.scale-x-0 { + --transform-scale-x: 0; +} + +.scale-x-50 { + --transform-scale-x: .5; +} + +.scale-x-75 { + --transform-scale-x: .75; +} + +.scale-x-90 { + --transform-scale-x: .9; +} + +.scale-x-95 { + --transform-scale-x: .95; +} + +.scale-x-100 { + --transform-scale-x: 1; +} + +.scale-x-105 { + --transform-scale-x: 1.05; +} + +.scale-x-110 { + --transform-scale-x: 1.1; +} + +.scale-x-125 { + --transform-scale-x: 1.25; +} + +.scale-x-150 { + --transform-scale-x: 1.5; +} + +.scale-y-0 { + --transform-scale-y: 0; +} + +.scale-y-50 { + --transform-scale-y: .5; +} + +.scale-y-75 { + --transform-scale-y: .75; +} + +.scale-y-90 { + --transform-scale-y: .9; +} + +.scale-y-95 { + --transform-scale-y: .95; +} + +.scale-y-100 { + --transform-scale-y: 1; +} + +.scale-y-105 { + --transform-scale-y: 1.05; +} + +.scale-y-110 { + --transform-scale-y: 1.1; +} + +.scale-y-125 { + --transform-scale-y: 1.25; +} + +.scale-y-150 { + --transform-scale-y: 1.5; +} + +.hover\:scale-0:hover { + --transform-scale-x: 0; + --transform-scale-y: 0; +} + +.hover\:scale-50:hover { + --transform-scale-x: .5; + --transform-scale-y: .5; +} + +.hover\:scale-75:hover { + --transform-scale-x: .75; + --transform-scale-y: .75; +} + +.hover\:scale-90:hover { + --transform-scale-x: .9; + --transform-scale-y: .9; +} + +.hover\:scale-95:hover { + --transform-scale-x: .95; + --transform-scale-y: .95; +} + +.hover\:scale-100:hover { + --transform-scale-x: 1; + --transform-scale-y: 1; +} + +.hover\:scale-105:hover { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; +} + +.hover\:scale-110:hover { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; +} + +.hover\:scale-125:hover { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; +} + +.hover\:scale-150:hover { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; +} + +.hover\:scale-x-0:hover { + --transform-scale-x: 0; +} + +.hover\:scale-x-50:hover { + --transform-scale-x: .5; +} + +.hover\:scale-x-75:hover { + --transform-scale-x: .75; +} + +.hover\:scale-x-90:hover { + --transform-scale-x: .9; +} + +.hover\:scale-x-95:hover { + --transform-scale-x: .95; +} + +.hover\:scale-x-100:hover { + --transform-scale-x: 1; +} + +.hover\:scale-x-105:hover { + --transform-scale-x: 1.05; +} + +.hover\:scale-x-110:hover { + --transform-scale-x: 1.1; +} + +.hover\:scale-x-125:hover { + --transform-scale-x: 1.25; +} + +.hover\:scale-x-150:hover { + --transform-scale-x: 1.5; +} + +.hover\:scale-y-0:hover { + --transform-scale-y: 0; +} + +.hover\:scale-y-50:hover { + --transform-scale-y: .5; +} + +.hover\:scale-y-75:hover { + --transform-scale-y: .75; +} + +.hover\:scale-y-90:hover { + --transform-scale-y: .9; +} + +.hover\:scale-y-95:hover { + --transform-scale-y: .95; +} + +.hover\:scale-y-100:hover { + --transform-scale-y: 1; +} + +.hover\:scale-y-105:hover { + --transform-scale-y: 1.05; +} + +.hover\:scale-y-110:hover { + --transform-scale-y: 1.1; +} + +.hover\:scale-y-125:hover { + --transform-scale-y: 1.25; +} + +.hover\:scale-y-150:hover { + --transform-scale-y: 1.5; +} + +.focus\:scale-0:focus { + --transform-scale-x: 0; + --transform-scale-y: 0; +} + +.focus\:scale-50:focus { + --transform-scale-x: .5; + --transform-scale-y: .5; +} + +.focus\:scale-75:focus { + --transform-scale-x: .75; + --transform-scale-y: .75; +} + +.focus\:scale-90:focus { + --transform-scale-x: .9; + --transform-scale-y: .9; +} + +.focus\:scale-95:focus { + --transform-scale-x: .95; + --transform-scale-y: .95; +} + +.focus\:scale-100:focus { + --transform-scale-x: 1; + --transform-scale-y: 1; +} + +.focus\:scale-105:focus { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; +} + +.focus\:scale-110:focus { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; +} + +.focus\:scale-125:focus { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; +} + +.focus\:scale-150:focus { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; +} + +.focus\:scale-x-0:focus { + --transform-scale-x: 0; +} + +.focus\:scale-x-50:focus { + --transform-scale-x: .5; +} + +.focus\:scale-x-75:focus { + --transform-scale-x: .75; +} + +.focus\:scale-x-90:focus { + --transform-scale-x: .9; +} + +.focus\:scale-x-95:focus { + --transform-scale-x: .95; +} + +.focus\:scale-x-100:focus { + --transform-scale-x: 1; +} + +.focus\:scale-x-105:focus { + --transform-scale-x: 1.05; +} + +.focus\:scale-x-110:focus { + --transform-scale-x: 1.1; +} + +.focus\:scale-x-125:focus { + --transform-scale-x: 1.25; +} + +.focus\:scale-x-150:focus { + --transform-scale-x: 1.5; +} + +.focus\:scale-y-0:focus { + --transform-scale-y: 0; +} + +.focus\:scale-y-50:focus { + --transform-scale-y: .5; +} + +.focus\:scale-y-75:focus { + --transform-scale-y: .75; +} + +.focus\:scale-y-90:focus { + --transform-scale-y: .9; +} + +.focus\:scale-y-95:focus { + --transform-scale-y: .95; +} + +.focus\:scale-y-100:focus { + --transform-scale-y: 1; +} + +.focus\:scale-y-105:focus { + --transform-scale-y: 1.05; +} + +.focus\:scale-y-110:focus { + --transform-scale-y: 1.1; +} + +.focus\:scale-y-125:focus { + --transform-scale-y: 1.25; +} + +.focus\:scale-y-150:focus { + --transform-scale-y: 1.5; +} + +.rotate-0 { + --transform-rotate: 0; +} + +.rotate-45 { + --transform-rotate: 45deg; +} + +.rotate-90 { + --transform-rotate: 90deg; +} + +.rotate-180 { + --transform-rotate: 180deg; +} + +.-rotate-180 { + --transform-rotate: -180deg; +} + +.-rotate-90 { + --transform-rotate: -90deg; +} + +.-rotate-45 { + --transform-rotate: -45deg; +} + +.hover\:rotate-0:hover { + --transform-rotate: 0; +} + +.hover\:rotate-45:hover { + --transform-rotate: 45deg; +} + +.hover\:rotate-90:hover { + --transform-rotate: 90deg; +} + +.hover\:rotate-180:hover { + --transform-rotate: 180deg; +} + +.hover\:-rotate-180:hover { + --transform-rotate: -180deg; +} + +.hover\:-rotate-90:hover { + --transform-rotate: -90deg; +} + +.hover\:-rotate-45:hover { + --transform-rotate: -45deg; +} + +.focus\:rotate-0:focus { + --transform-rotate: 0; +} + +.focus\:rotate-45:focus { + --transform-rotate: 45deg; +} + +.focus\:rotate-90:focus { + --transform-rotate: 90deg; +} + +.focus\:rotate-180:focus { + --transform-rotate: 180deg; +} + +.focus\:-rotate-180:focus { + --transform-rotate: -180deg; +} + +.focus\:-rotate-90:focus { + --transform-rotate: -90deg; +} + +.focus\:-rotate-45:focus { + --transform-rotate: -45deg; +} + +.translate-x-0 { + --transform-translate-x: 0; +} + +.translate-x-1 { + --transform-translate-x: 0.25rem; +} + +.translate-x-2 { + --transform-translate-x: 0.5rem; +} + +.translate-x-3 { + --transform-translate-x: 0.75rem; +} + +.translate-x-4 { + --transform-translate-x: 1rem; +} + +.translate-x-5 { + --transform-translate-x: 1.25rem; +} + +.translate-x-6 { + --transform-translate-x: 1.5rem; +} + +.translate-x-8 { + --transform-translate-x: 2rem; +} + +.translate-x-10 { + --transform-translate-x: 2.5rem; +} + +.translate-x-12 { + --transform-translate-x: 3rem; +} + +.translate-x-16 { + --transform-translate-x: 4rem; +} + +.translate-x-20 { + --transform-translate-x: 5rem; +} + +.translate-x-24 { + --transform-translate-x: 6rem; +} + +.translate-x-32 { + --transform-translate-x: 8rem; +} + +.translate-x-40 { + --transform-translate-x: 10rem; +} + +.translate-x-48 { + --transform-translate-x: 12rem; +} + +.translate-x-56 { + --transform-translate-x: 14rem; +} + +.translate-x-64 { + --transform-translate-x: 16rem; +} + +.translate-x-px { + --transform-translate-x: 1px; +} + +.-translate-x-1 { + --transform-translate-x: -0.25rem; +} + +.-translate-x-2 { + --transform-translate-x: -0.5rem; +} + +.-translate-x-3 { + --transform-translate-x: -0.75rem; +} + +.-translate-x-4 { + --transform-translate-x: -1rem; +} + +.-translate-x-5 { + --transform-translate-x: -1.25rem; +} + +.-translate-x-6 { + --transform-translate-x: -1.5rem; +} + +.-translate-x-8 { + --transform-translate-x: -2rem; +} + +.-translate-x-10 { + --transform-translate-x: -2.5rem; +} + +.-translate-x-12 { + --transform-translate-x: -3rem; +} + +.-translate-x-16 { + --transform-translate-x: -4rem; +} + +.-translate-x-20 { + --transform-translate-x: -5rem; +} + +.-translate-x-24 { + --transform-translate-x: -6rem; +} + +.-translate-x-32 { + --transform-translate-x: -8rem; +} + +.-translate-x-40 { + --transform-translate-x: -10rem; +} + +.-translate-x-48 { + --transform-translate-x: -12rem; +} + +.-translate-x-56 { + --transform-translate-x: -14rem; +} + +.-translate-x-64 { + --transform-translate-x: -16rem; +} + +.-translate-x-px { + --transform-translate-x: -1px; +} + +.-translate-x-full { + --transform-translate-x: -100%; +} + +.-translate-x-1\/2 { + --transform-translate-x: -50%; +} + +.translate-x-1\/2 { + --transform-translate-x: 50%; +} + +.translate-x-full { + --transform-translate-x: 100%; +} + +.translate-y-0 { + --transform-translate-y: 0; +} + +.translate-y-1 { + --transform-translate-y: 0.25rem; +} + +.translate-y-2 { + --transform-translate-y: 0.5rem; +} + +.translate-y-3 { + --transform-translate-y: 0.75rem; +} + +.translate-y-4 { + --transform-translate-y: 1rem; +} + +.translate-y-5 { + --transform-translate-y: 1.25rem; +} + +.translate-y-6 { + --transform-translate-y: 1.5rem; +} + +.translate-y-8 { + --transform-translate-y: 2rem; +} + +.translate-y-10 { + --transform-translate-y: 2.5rem; +} + +.translate-y-12 { + --transform-translate-y: 3rem; +} + +.translate-y-16 { + --transform-translate-y: 4rem; +} + +.translate-y-20 { + --transform-translate-y: 5rem; +} + +.translate-y-24 { + --transform-translate-y: 6rem; +} + +.translate-y-32 { + --transform-translate-y: 8rem; +} + +.translate-y-40 { + --transform-translate-y: 10rem; +} + +.translate-y-48 { + --transform-translate-y: 12rem; +} + +.translate-y-56 { + --transform-translate-y: 14rem; +} + +.translate-y-64 { + --transform-translate-y: 16rem; +} + +.translate-y-px { + --transform-translate-y: 1px; +} + +.-translate-y-1 { + --transform-translate-y: -0.25rem; +} + +.-translate-y-2 { + --transform-translate-y: -0.5rem; +} + +.-translate-y-3 { + --transform-translate-y: -0.75rem; +} + +.-translate-y-4 { + --transform-translate-y: -1rem; +} + +.-translate-y-5 { + --transform-translate-y: -1.25rem; +} + +.-translate-y-6 { + --transform-translate-y: -1.5rem; +} + +.-translate-y-8 { + --transform-translate-y: -2rem; +} + +.-translate-y-10 { + --transform-translate-y: -2.5rem; +} + +.-translate-y-12 { + --transform-translate-y: -3rem; +} + +.-translate-y-16 { + --transform-translate-y: -4rem; +} + +.-translate-y-20 { + --transform-translate-y: -5rem; +} + +.-translate-y-24 { + --transform-translate-y: -6rem; +} + +.-translate-y-32 { + --transform-translate-y: -8rem; +} + +.-translate-y-40 { + --transform-translate-y: -10rem; +} + +.-translate-y-48 { + --transform-translate-y: -12rem; +} + +.-translate-y-56 { + --transform-translate-y: -14rem; +} + +.-translate-y-64 { + --transform-translate-y: -16rem; +} + +.-translate-y-px { + --transform-translate-y: -1px; +} + +.-translate-y-full { + --transform-translate-y: -100%; +} + +.-translate-y-1\/2 { + --transform-translate-y: -50%; +} + +.translate-y-1\/2 { + --transform-translate-y: 50%; +} + +.translate-y-full { + --transform-translate-y: 100%; +} + +.hover\:translate-x-0:hover { + --transform-translate-x: 0; +} + +.hover\:translate-x-1:hover { + --transform-translate-x: 0.25rem; +} + +.hover\:translate-x-2:hover { + --transform-translate-x: 0.5rem; +} + +.hover\:translate-x-3:hover { + --transform-translate-x: 0.75rem; +} + +.hover\:translate-x-4:hover { + --transform-translate-x: 1rem; +} + +.hover\:translate-x-5:hover { + --transform-translate-x: 1.25rem; +} + +.hover\:translate-x-6:hover { + --transform-translate-x: 1.5rem; +} + +.hover\:translate-x-8:hover { + --transform-translate-x: 2rem; +} + +.hover\:translate-x-10:hover { + --transform-translate-x: 2.5rem; +} + +.hover\:translate-x-12:hover { + --transform-translate-x: 3rem; +} + +.hover\:translate-x-16:hover { + --transform-translate-x: 4rem; +} + +.hover\:translate-x-20:hover { + --transform-translate-x: 5rem; +} + +.hover\:translate-x-24:hover { + --transform-translate-x: 6rem; +} + +.hover\:translate-x-32:hover { + --transform-translate-x: 8rem; +} + +.hover\:translate-x-40:hover { + --transform-translate-x: 10rem; +} + +.hover\:translate-x-48:hover { + --transform-translate-x: 12rem; +} + +.hover\:translate-x-56:hover { + --transform-translate-x: 14rem; +} + +.hover\:translate-x-64:hover { + --transform-translate-x: 16rem; +} + +.hover\:translate-x-px:hover { + --transform-translate-x: 1px; +} + +.hover\:-translate-x-1:hover { + --transform-translate-x: -0.25rem; +} + +.hover\:-translate-x-2:hover { + --transform-translate-x: -0.5rem; +} + +.hover\:-translate-x-3:hover { + --transform-translate-x: -0.75rem; +} + +.hover\:-translate-x-4:hover { + --transform-translate-x: -1rem; +} + +.hover\:-translate-x-5:hover { + --transform-translate-x: -1.25rem; +} + +.hover\:-translate-x-6:hover { + --transform-translate-x: -1.5rem; +} + +.hover\:-translate-x-8:hover { + --transform-translate-x: -2rem; +} + +.hover\:-translate-x-10:hover { + --transform-translate-x: -2.5rem; +} + +.hover\:-translate-x-12:hover { + --transform-translate-x: -3rem; +} + +.hover\:-translate-x-16:hover { + --transform-translate-x: -4rem; +} + +.hover\:-translate-x-20:hover { + --transform-translate-x: -5rem; +} + +.hover\:-translate-x-24:hover { + --transform-translate-x: -6rem; +} + +.hover\:-translate-x-32:hover { + --transform-translate-x: -8rem; +} + +.hover\:-translate-x-40:hover { + --transform-translate-x: -10rem; +} + +.hover\:-translate-x-48:hover { + --transform-translate-x: -12rem; +} + +.hover\:-translate-x-56:hover { + --transform-translate-x: -14rem; +} + +.hover\:-translate-x-64:hover { + --transform-translate-x: -16rem; +} + +.hover\:-translate-x-px:hover { + --transform-translate-x: -1px; +} + +.hover\:-translate-x-full:hover { + --transform-translate-x: -100%; +} + +.hover\:-translate-x-1\/2:hover { + --transform-translate-x: -50%; +} + +.hover\:translate-x-1\/2:hover { + --transform-translate-x: 50%; +} + +.hover\:translate-x-full:hover { + --transform-translate-x: 100%; +} + +.hover\:translate-y-0:hover { + --transform-translate-y: 0; +} + +.hover\:translate-y-1:hover { + --transform-translate-y: 0.25rem; +} + +.hover\:translate-y-2:hover { + --transform-translate-y: 0.5rem; +} + +.hover\:translate-y-3:hover { + --transform-translate-y: 0.75rem; +} + +.hover\:translate-y-4:hover { + --transform-translate-y: 1rem; +} + +.hover\:translate-y-5:hover { + --transform-translate-y: 1.25rem; +} + +.hover\:translate-y-6:hover { + --transform-translate-y: 1.5rem; +} + +.hover\:translate-y-8:hover { + --transform-translate-y: 2rem; +} + +.hover\:translate-y-10:hover { + --transform-translate-y: 2.5rem; +} + +.hover\:translate-y-12:hover { + --transform-translate-y: 3rem; +} + +.hover\:translate-y-16:hover { + --transform-translate-y: 4rem; +} + +.hover\:translate-y-20:hover { + --transform-translate-y: 5rem; +} + +.hover\:translate-y-24:hover { + --transform-translate-y: 6rem; +} + +.hover\:translate-y-32:hover { + --transform-translate-y: 8rem; +} + +.hover\:translate-y-40:hover { + --transform-translate-y: 10rem; +} + +.hover\:translate-y-48:hover { + --transform-translate-y: 12rem; +} + +.hover\:translate-y-56:hover { + --transform-translate-y: 14rem; +} + +.hover\:translate-y-64:hover { + --transform-translate-y: 16rem; +} + +.hover\:translate-y-px:hover { + --transform-translate-y: 1px; +} + +.hover\:-translate-y-1:hover { + --transform-translate-y: -0.25rem; +} + +.hover\:-translate-y-2:hover { + --transform-translate-y: -0.5rem; +} + +.hover\:-translate-y-3:hover { + --transform-translate-y: -0.75rem; +} + +.hover\:-translate-y-4:hover { + --transform-translate-y: -1rem; +} + +.hover\:-translate-y-5:hover { + --transform-translate-y: -1.25rem; +} + +.hover\:-translate-y-6:hover { + --transform-translate-y: -1.5rem; +} + +.hover\:-translate-y-8:hover { + --transform-translate-y: -2rem; +} + +.hover\:-translate-y-10:hover { + --transform-translate-y: -2.5rem; +} + +.hover\:-translate-y-12:hover { + --transform-translate-y: -3rem; +} + +.hover\:-translate-y-16:hover { + --transform-translate-y: -4rem; +} + +.hover\:-translate-y-20:hover { + --transform-translate-y: -5rem; +} + +.hover\:-translate-y-24:hover { + --transform-translate-y: -6rem; +} + +.hover\:-translate-y-32:hover { + --transform-translate-y: -8rem; +} + +.hover\:-translate-y-40:hover { + --transform-translate-y: -10rem; +} + +.hover\:-translate-y-48:hover { + --transform-translate-y: -12rem; +} + +.hover\:-translate-y-56:hover { + --transform-translate-y: -14rem; +} + +.hover\:-translate-y-64:hover { + --transform-translate-y: -16rem; +} + +.hover\:-translate-y-px:hover { + --transform-translate-y: -1px; +} + +.hover\:-translate-y-full:hover { + --transform-translate-y: -100%; +} + +.hover\:-translate-y-1\/2:hover { + --transform-translate-y: -50%; +} + +.hover\:translate-y-1\/2:hover { + --transform-translate-y: 50%; +} + +.hover\:translate-y-full:hover { + --transform-translate-y: 100%; +} + +.focus\:translate-x-0:focus { + --transform-translate-x: 0; +} + +.focus\:translate-x-1:focus { + --transform-translate-x: 0.25rem; +} + +.focus\:translate-x-2:focus { + --transform-translate-x: 0.5rem; +} + +.focus\:translate-x-3:focus { + --transform-translate-x: 0.75rem; +} + +.focus\:translate-x-4:focus { + --transform-translate-x: 1rem; +} + +.focus\:translate-x-5:focus { + --transform-translate-x: 1.25rem; +} + +.focus\:translate-x-6:focus { + --transform-translate-x: 1.5rem; +} + +.focus\:translate-x-8:focus { + --transform-translate-x: 2rem; +} + +.focus\:translate-x-10:focus { + --transform-translate-x: 2.5rem; +} + +.focus\:translate-x-12:focus { + --transform-translate-x: 3rem; +} + +.focus\:translate-x-16:focus { + --transform-translate-x: 4rem; +} + +.focus\:translate-x-20:focus { + --transform-translate-x: 5rem; +} + +.focus\:translate-x-24:focus { + --transform-translate-x: 6rem; +} + +.focus\:translate-x-32:focus { + --transform-translate-x: 8rem; +} + +.focus\:translate-x-40:focus { + --transform-translate-x: 10rem; +} + +.focus\:translate-x-48:focus { + --transform-translate-x: 12rem; +} + +.focus\:translate-x-56:focus { + --transform-translate-x: 14rem; +} + +.focus\:translate-x-64:focus { + --transform-translate-x: 16rem; +} + +.focus\:translate-x-px:focus { + --transform-translate-x: 1px; +} + +.focus\:-translate-x-1:focus { + --transform-translate-x: -0.25rem; +} + +.focus\:-translate-x-2:focus { + --transform-translate-x: -0.5rem; +} + +.focus\:-translate-x-3:focus { + --transform-translate-x: -0.75rem; +} + +.focus\:-translate-x-4:focus { + --transform-translate-x: -1rem; +} + +.focus\:-translate-x-5:focus { + --transform-translate-x: -1.25rem; +} + +.focus\:-translate-x-6:focus { + --transform-translate-x: -1.5rem; +} + +.focus\:-translate-x-8:focus { + --transform-translate-x: -2rem; +} + +.focus\:-translate-x-10:focus { + --transform-translate-x: -2.5rem; +} + +.focus\:-translate-x-12:focus { + --transform-translate-x: -3rem; +} + +.focus\:-translate-x-16:focus { + --transform-translate-x: -4rem; +} + +.focus\:-translate-x-20:focus { + --transform-translate-x: -5rem; +} + +.focus\:-translate-x-24:focus { + --transform-translate-x: -6rem; +} + +.focus\:-translate-x-32:focus { + --transform-translate-x: -8rem; +} + +.focus\:-translate-x-40:focus { + --transform-translate-x: -10rem; +} + +.focus\:-translate-x-48:focus { + --transform-translate-x: -12rem; +} + +.focus\:-translate-x-56:focus { + --transform-translate-x: -14rem; +} + +.focus\:-translate-x-64:focus { + --transform-translate-x: -16rem; +} + +.focus\:-translate-x-px:focus { + --transform-translate-x: -1px; +} + +.focus\:-translate-x-full:focus { + --transform-translate-x: -100%; +} + +.focus\:-translate-x-1\/2:focus { + --transform-translate-x: -50%; +} + +.focus\:translate-x-1\/2:focus { + --transform-translate-x: 50%; +} + +.focus\:translate-x-full:focus { + --transform-translate-x: 100%; +} + +.focus\:translate-y-0:focus { + --transform-translate-y: 0; +} + +.focus\:translate-y-1:focus { + --transform-translate-y: 0.25rem; +} + +.focus\:translate-y-2:focus { + --transform-translate-y: 0.5rem; +} + +.focus\:translate-y-3:focus { + --transform-translate-y: 0.75rem; +} + +.focus\:translate-y-4:focus { + --transform-translate-y: 1rem; +} + +.focus\:translate-y-5:focus { + --transform-translate-y: 1.25rem; +} + +.focus\:translate-y-6:focus { + --transform-translate-y: 1.5rem; +} + +.focus\:translate-y-8:focus { + --transform-translate-y: 2rem; +} + +.focus\:translate-y-10:focus { + --transform-translate-y: 2.5rem; +} + +.focus\:translate-y-12:focus { + --transform-translate-y: 3rem; +} + +.focus\:translate-y-16:focus { + --transform-translate-y: 4rem; +} + +.focus\:translate-y-20:focus { + --transform-translate-y: 5rem; +} + +.focus\:translate-y-24:focus { + --transform-translate-y: 6rem; +} + +.focus\:translate-y-32:focus { + --transform-translate-y: 8rem; +} + +.focus\:translate-y-40:focus { + --transform-translate-y: 10rem; +} + +.focus\:translate-y-48:focus { + --transform-translate-y: 12rem; +} + +.focus\:translate-y-56:focus { + --transform-translate-y: 14rem; +} + +.focus\:translate-y-64:focus { + --transform-translate-y: 16rem; +} + +.focus\:translate-y-px:focus { + --transform-translate-y: 1px; +} + +.focus\:-translate-y-1:focus { + --transform-translate-y: -0.25rem; +} + +.focus\:-translate-y-2:focus { + --transform-translate-y: -0.5rem; +} + +.focus\:-translate-y-3:focus { + --transform-translate-y: -0.75rem; +} + +.focus\:-translate-y-4:focus { + --transform-translate-y: -1rem; +} + +.focus\:-translate-y-5:focus { + --transform-translate-y: -1.25rem; +} + +.focus\:-translate-y-6:focus { + --transform-translate-y: -1.5rem; +} + +.focus\:-translate-y-8:focus { + --transform-translate-y: -2rem; +} + +.focus\:-translate-y-10:focus { + --transform-translate-y: -2.5rem; +} + +.focus\:-translate-y-12:focus { + --transform-translate-y: -3rem; +} + +.focus\:-translate-y-16:focus { + --transform-translate-y: -4rem; +} + +.focus\:-translate-y-20:focus { + --transform-translate-y: -5rem; +} + +.focus\:-translate-y-24:focus { + --transform-translate-y: -6rem; +} + +.focus\:-translate-y-32:focus { + --transform-translate-y: -8rem; +} + +.focus\:-translate-y-40:focus { + --transform-translate-y: -10rem; +} + +.focus\:-translate-y-48:focus { + --transform-translate-y: -12rem; +} + +.focus\:-translate-y-56:focus { + --transform-translate-y: -14rem; +} + +.focus\:-translate-y-64:focus { + --transform-translate-y: -16rem; +} + +.focus\:-translate-y-px:focus { + --transform-translate-y: -1px; +} + +.focus\:-translate-y-full:focus { + --transform-translate-y: -100%; +} + +.focus\:-translate-y-1\/2:focus { + --transform-translate-y: -50%; +} + +.focus\:translate-y-1\/2:focus { + --transform-translate-y: 50%; +} + +.focus\:translate-y-full:focus { + --transform-translate-y: 100%; +} + +.skew-x-0 { + --transform-skew-x: 0; +} + +.skew-x-3 { + --transform-skew-x: 3deg; +} + +.skew-x-6 { + --transform-skew-x: 6deg; +} + +.skew-x-12 { + --transform-skew-x: 12deg; +} + +.-skew-x-12 { + --transform-skew-x: -12deg; +} + +.-skew-x-6 { + --transform-skew-x: -6deg; +} + +.-skew-x-3 { + --transform-skew-x: -3deg; +} + +.skew-y-0 { + --transform-skew-y: 0; +} + +.skew-y-3 { + --transform-skew-y: 3deg; +} + +.skew-y-6 { + --transform-skew-y: 6deg; +} + +.skew-y-12 { + --transform-skew-y: 12deg; +} + +.-skew-y-12 { + --transform-skew-y: -12deg; +} + +.-skew-y-6 { + --transform-skew-y: -6deg; +} + +.-skew-y-3 { + --transform-skew-y: -3deg; +} + +.hover\:skew-x-0:hover { + --transform-skew-x: 0; +} + +.hover\:skew-x-3:hover { + --transform-skew-x: 3deg; +} + +.hover\:skew-x-6:hover { + --transform-skew-x: 6deg; +} + +.hover\:skew-x-12:hover { + --transform-skew-x: 12deg; +} + +.hover\:-skew-x-12:hover { + --transform-skew-x: -12deg; +} + +.hover\:-skew-x-6:hover { + --transform-skew-x: -6deg; +} + +.hover\:-skew-x-3:hover { + --transform-skew-x: -3deg; +} + +.hover\:skew-y-0:hover { + --transform-skew-y: 0; +} + +.hover\:skew-y-3:hover { + --transform-skew-y: 3deg; +} + +.hover\:skew-y-6:hover { + --transform-skew-y: 6deg; +} + +.hover\:skew-y-12:hover { + --transform-skew-y: 12deg; +} + +.hover\:-skew-y-12:hover { + --transform-skew-y: -12deg; +} + +.hover\:-skew-y-6:hover { + --transform-skew-y: -6deg; +} + +.hover\:-skew-y-3:hover { + --transform-skew-y: -3deg; +} + +.focus\:skew-x-0:focus { + --transform-skew-x: 0; +} + +.focus\:skew-x-3:focus { + --transform-skew-x: 3deg; +} + +.focus\:skew-x-6:focus { + --transform-skew-x: 6deg; +} + +.focus\:skew-x-12:focus { + --transform-skew-x: 12deg; +} + +.focus\:-skew-x-12:focus { + --transform-skew-x: -12deg; +} + +.focus\:-skew-x-6:focus { + --transform-skew-x: -6deg; +} + +.focus\:-skew-x-3:focus { + --transform-skew-x: -3deg; +} + +.focus\:skew-y-0:focus { + --transform-skew-y: 0; +} + +.focus\:skew-y-3:focus { + --transform-skew-y: 3deg; +} + +.focus\:skew-y-6:focus { + --transform-skew-y: 6deg; +} + +.focus\:skew-y-12:focus { + --transform-skew-y: 12deg; +} + +.focus\:-skew-y-12:focus { + --transform-skew-y: -12deg; +} + +.focus\:-skew-y-6:focus { + --transform-skew-y: -6deg; +} + +.focus\:-skew-y-3:focus { + --transform-skew-y: -3deg; +} + +.transition-none { + transition-property: none; +} + +.transition-all { + transition-property: all; +} + +.transition { + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; +} + +.transition-colors { + transition-property: background-color, border-color, color, fill, stroke; +} + +.transition-opacity { + transition-property: opacity; +} + +.transition-shadow { + transition-property: box-shadow; +} + +.transition-transform { + transition-property: transform; +} + +.ease-linear { + transition-timing-function: linear; +} + +.ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); +} + +.ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.duration-75 { + transition-duration: 75ms; +} + +.duration-100 { + transition-duration: 100ms; +} + +.duration-150 { + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.duration-500 { + transition-duration: 500ms; +} + +.duration-700 { + transition-duration: 700ms; +} + +.duration-1000 { + transition-duration: 1000ms; +} + +.delay-75 { + transition-delay: 75ms; +} + +.delay-100 { + transition-delay: 100ms; +} + +.delay-150 { + transition-delay: 150ms; +} + +.delay-200 { + transition-delay: 200ms; +} + +.delay-300 { + transition-delay: 300ms; +} + +.delay-500 { + transition-delay: 500ms; +} + +.delay-700 { + transition-delay: 700ms; +} + +.delay-1000 { + transition-delay: 1000ms; +} + +@-webkit-keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@-webkit-keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} + +@keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} + +@-webkit-keyframes pulse { + 50% { + opacity: .5; + } +} + +@keyframes pulse { + 50% { + opacity: .5; + } +} + +@-webkit-keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); + animation-timing-function: cubic-bezier(0.8,0,1,1); + } + + 50% { + transform: none; + -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); + animation-timing-function: cubic-bezier(0,0,0.2,1); + } +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); + animation-timing-function: cubic-bezier(0.8,0,1,1); + } + + 50% { + transform: none; + -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); + animation-timing-function: cubic-bezier(0,0,0.2,1); + } +} + +.animate-none { + -webkit-animation: none; + animation: none; +} + +.animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; +} + +.animate-ping { + -webkit-animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; +} + +.animate-pulse { + -webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +.animate-bounce { + -webkit-animation: bounce 1s infinite; + animation: bounce 1s infinite; +} + +@media (min-width: 640px) { + .sm\:container { + width: 100%; + } + + @media (min-width: 640px) { + .sm\:container { + max-width: 640px; + } + } + + @media (min-width: 768px) { + .sm\:container { + max-width: 768px; + } + } + + @media (min-width: 1024px) { + .sm\:container { + max-width: 1024px; + } + } + + @media (min-width: 1280px) { + .sm\:container { + max-width: 1280px; + } + } + + .sm\:space-y-0 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0px * var(--space-y-reverse)); + } + + .sm\:space-x-0 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0px * var(--space-x-reverse)); + margin-left: calc(0px * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.25rem * var(--space-y-reverse)); + } + + .sm\:space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.25rem * var(--space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.5rem * var(--space-y-reverse)); + } + + .sm\:space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.5rem * var(--space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.75rem * var(--space-y-reverse)); + } + + .sm\:space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.75rem * var(--space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1rem * var(--space-y-reverse)); + } + + .sm\:space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1rem * var(--space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.25rem * var(--space-y-reverse)); + } + + .sm\:space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.25rem * var(--space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.5rem * var(--space-y-reverse)); + } + + .sm\:space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.5rem * var(--space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2rem * var(--space-y-reverse)); + } + + .sm\:space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2rem * var(--space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2.5rem * var(--space-y-reverse)); + } + + .sm\:space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2.5rem * var(--space-x-reverse)); + margin-left: calc(2.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(3rem * var(--space-y-reverse)); + } + + .sm\:space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(3rem * var(--space-x-reverse)); + margin-left: calc(3rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(4rem * var(--space-y-reverse)); + } + + .sm\:space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(4rem * var(--space-x-reverse)); + margin-left: calc(4rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(5rem * var(--space-y-reverse)); + } + + .sm\:space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(5rem * var(--space-x-reverse)); + margin-left: calc(5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(6rem * var(--space-y-reverse)); + } + + .sm\:space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(6rem * var(--space-x-reverse)); + margin-left: calc(6rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(8rem * var(--space-y-reverse)); + } + + .sm\:space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(8rem * var(--space-x-reverse)); + margin-left: calc(8rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(10rem * var(--space-y-reverse)); + } + + .sm\:space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(10rem * var(--space-x-reverse)); + margin-left: calc(10rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(12rem * var(--space-y-reverse)); + } + + .sm\:space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(12rem * var(--space-x-reverse)); + margin-left: calc(12rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(14rem * var(--space-y-reverse)); + } + + .sm\:space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(14rem * var(--space-x-reverse)); + margin-left: calc(14rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(16rem * var(--space-y-reverse)); + } + + .sm\:space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(16rem * var(--space-x-reverse)); + margin-left: calc(16rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1px * var(--space-y-reverse)); + } + + .sm\:space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1px * var(--space-x-reverse)); + margin-left: calc(1px * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.25rem * var(--space-y-reverse)); + } + + .sm\:-space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.25rem * var(--space-x-reverse)); + margin-left: calc(-0.25rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.5rem * var(--space-y-reverse)); + } + + .sm\:-space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.5rem * var(--space-x-reverse)); + margin-left: calc(-0.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.75rem * var(--space-y-reverse)); + } + + .sm\:-space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.75rem * var(--space-x-reverse)); + margin-left: calc(-0.75rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1rem * var(--space-y-reverse)); + } + + .sm\:-space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1rem * var(--space-x-reverse)); + margin-left: calc(-1rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.25rem * var(--space-y-reverse)); + } + + .sm\:-space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.25rem * var(--space-x-reverse)); + margin-left: calc(-1.25rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.5rem * var(--space-y-reverse)); + } + + .sm\:-space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.5rem * var(--space-x-reverse)); + margin-left: calc(-1.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2rem * var(--space-y-reverse)); + } + + .sm\:-space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2rem * var(--space-x-reverse)); + margin-left: calc(-2rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2.5rem * var(--space-y-reverse)); + } + + .sm\:-space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2.5rem * var(--space-x-reverse)); + margin-left: calc(-2.5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-3rem * var(--space-y-reverse)); + } + + .sm\:-space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-3rem * var(--space-x-reverse)); + margin-left: calc(-3rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-4rem * var(--space-y-reverse)); + } + + .sm\:-space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-4rem * var(--space-x-reverse)); + margin-left: calc(-4rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-5rem * var(--space-y-reverse)); + } + + .sm\:-space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-5rem * var(--space-x-reverse)); + margin-left: calc(-5rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-6rem * var(--space-y-reverse)); + } + + .sm\:-space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-6rem * var(--space-x-reverse)); + margin-left: calc(-6rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-8rem * var(--space-y-reverse)); + } + + .sm\:-space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-8rem * var(--space-x-reverse)); + margin-left: calc(-8rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-10rem * var(--space-y-reverse)); + } + + .sm\:-space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-10rem * var(--space-x-reverse)); + margin-left: calc(-10rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-12rem * var(--space-y-reverse)); + } + + .sm\:-space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-12rem * var(--space-x-reverse)); + margin-left: calc(-12rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-14rem * var(--space-y-reverse)); + } + + .sm\:-space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-14rem * var(--space-x-reverse)); + margin-left: calc(-14rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-16rem * var(--space-y-reverse)); + } + + .sm\:-space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-16rem * var(--space-x-reverse)); + margin-left: calc(-16rem * calc(1 - var(--space-x-reverse))); + } + + .sm\:-space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1px * var(--space-y-reverse)); + } + + .sm\:-space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1px * var(--space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--space-x-reverse))); + } + + .sm\:space-y-reverse > :not(template) ~ :not(template) { + --space-y-reverse: 1; + } + + .sm\:space-x-reverse > :not(template) ~ :not(template) { + --space-x-reverse: 1; + } + + .sm\:divide-y-0 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(0px * var(--divide-y-reverse)); + } + + .sm\:divide-x-0 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(0px * var(--divide-x-reverse)); + border-left-width: calc(0px * calc(1 - var(--divide-x-reverse))); + } + + .sm\:divide-y-2 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(2px * var(--divide-y-reverse)); + } + + .sm\:divide-x-2 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(2px * var(--divide-x-reverse)); + border-left-width: calc(2px * calc(1 - var(--divide-x-reverse))); + } + + .sm\:divide-y-4 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(4px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(4px * var(--divide-y-reverse)); + } + + .sm\:divide-x-4 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(4px * var(--divide-x-reverse)); + border-left-width: calc(4px * calc(1 - var(--divide-x-reverse))); + } + + .sm\:divide-y-8 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(8px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(8px * var(--divide-y-reverse)); + } + + .sm\:divide-x-8 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(8px * var(--divide-x-reverse)); + border-left-width: calc(8px * calc(1 - var(--divide-x-reverse))); + } + + .sm\:divide-y > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(1px * var(--divide-y-reverse)); + } + + .sm\:divide-x > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(1px * var(--divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--divide-x-reverse))); + } + + .sm\:divide-y-reverse > :not(template) ~ :not(template) { + --divide-y-reverse: 1; + } + + .sm\:divide-x-reverse > :not(template) ~ :not(template) { + --divide-x-reverse: 1; + } + + .sm\:divide-transparent > :not(template) ~ :not(template) { + border-color: transparent; + } + + .sm\:divide-current > :not(template) ~ :not(template) { + border-color: currentColor; + } + + .sm\:divide-black > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--divide-opacity)); + } + + .sm\:divide-white > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--divide-opacity)); + } + + .sm\:divide-gray-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--divide-opacity)); + } + + .sm\:divide-gray-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--divide-opacity)); + } + + .sm\:divide-gray-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--divide-opacity)); + } + + .sm\:divide-gray-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--divide-opacity)); + } + + .sm\:divide-gray-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--divide-opacity)); + } + + .sm\:divide-gray-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--divide-opacity)); + } + + .sm\:divide-gray-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--divide-opacity)); + } + + .sm\:divide-gray-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--divide-opacity)); + } + + .sm\:divide-gray-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--divide-opacity)); + } + + .sm\:divide-red-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--divide-opacity)); + } + + .sm\:divide-red-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--divide-opacity)); + } + + .sm\:divide-red-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--divide-opacity)); + } + + .sm\:divide-red-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--divide-opacity)); + } + + .sm\:divide-red-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--divide-opacity)); + } + + .sm\:divide-red-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--divide-opacity)); + } + + .sm\:divide-red-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--divide-opacity)); + } + + .sm\:divide-red-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--divide-opacity)); + } + + .sm\:divide-red-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--divide-opacity)); + } + + .sm\:divide-orange-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--divide-opacity)); + } + + .sm\:divide-orange-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--divide-opacity)); + } + + .sm\:divide-orange-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--divide-opacity)); + } + + .sm\:divide-orange-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--divide-opacity)); + } + + .sm\:divide-orange-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--divide-opacity)); + } + + .sm\:divide-orange-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--divide-opacity)); + } + + .sm\:divide-orange-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--divide-opacity)); + } + + .sm\:divide-orange-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--divide-opacity)); + } + + .sm\:divide-orange-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--divide-opacity)); + } + + .sm\:divide-yellow-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--divide-opacity)); + } + + .sm\:divide-yellow-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--divide-opacity)); + } + + .sm\:divide-yellow-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--divide-opacity)); + } + + .sm\:divide-yellow-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--divide-opacity)); + } + + .sm\:divide-yellow-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--divide-opacity)); + } + + .sm\:divide-yellow-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--divide-opacity)); + } + + .sm\:divide-yellow-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--divide-opacity)); + } + + .sm\:divide-yellow-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--divide-opacity)); + } + + .sm\:divide-yellow-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--divide-opacity)); + } + + .sm\:divide-green-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--divide-opacity)); + } + + .sm\:divide-green-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--divide-opacity)); + } + + .sm\:divide-green-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--divide-opacity)); + } + + .sm\:divide-green-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--divide-opacity)); + } + + .sm\:divide-green-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--divide-opacity)); + } + + .sm\:divide-green-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--divide-opacity)); + } + + .sm\:divide-green-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--divide-opacity)); + } + + .sm\:divide-green-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--divide-opacity)); + } + + .sm\:divide-green-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--divide-opacity)); + } + + .sm\:divide-teal-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--divide-opacity)); + } + + .sm\:divide-teal-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--divide-opacity)); + } + + .sm\:divide-teal-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--divide-opacity)); + } + + .sm\:divide-teal-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--divide-opacity)); + } + + .sm\:divide-teal-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--divide-opacity)); + } + + .sm\:divide-teal-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--divide-opacity)); + } + + .sm\:divide-teal-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--divide-opacity)); + } + + .sm\:divide-teal-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--divide-opacity)); + } + + .sm\:divide-teal-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--divide-opacity)); + } + + .sm\:divide-blue-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--divide-opacity)); + } + + .sm\:divide-blue-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--divide-opacity)); + } + + .sm\:divide-blue-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--divide-opacity)); + } + + .sm\:divide-blue-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--divide-opacity)); + } + + .sm\:divide-blue-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--divide-opacity)); + } + + .sm\:divide-blue-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--divide-opacity)); + } + + .sm\:divide-blue-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--divide-opacity)); + } + + .sm\:divide-blue-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--divide-opacity)); + } + + .sm\:divide-blue-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--divide-opacity)); + } + + .sm\:divide-indigo-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--divide-opacity)); + } + + .sm\:divide-indigo-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--divide-opacity)); + } + + .sm\:divide-indigo-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--divide-opacity)); + } + + .sm\:divide-indigo-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--divide-opacity)); + } + + .sm\:divide-indigo-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--divide-opacity)); + } + + .sm\:divide-indigo-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--divide-opacity)); + } + + .sm\:divide-indigo-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--divide-opacity)); + } + + .sm\:divide-indigo-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--divide-opacity)); + } + + .sm\:divide-indigo-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--divide-opacity)); + } + + .sm\:divide-purple-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--divide-opacity)); + } + + .sm\:divide-purple-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--divide-opacity)); + } + + .sm\:divide-purple-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--divide-opacity)); + } + + .sm\:divide-purple-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--divide-opacity)); + } + + .sm\:divide-purple-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--divide-opacity)); + } + + .sm\:divide-purple-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--divide-opacity)); + } + + .sm\:divide-purple-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--divide-opacity)); + } + + .sm\:divide-purple-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--divide-opacity)); + } + + .sm\:divide-purple-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--divide-opacity)); + } + + .sm\:divide-pink-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--divide-opacity)); + } + + .sm\:divide-pink-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--divide-opacity)); + } + + .sm\:divide-pink-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--divide-opacity)); + } + + .sm\:divide-pink-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--divide-opacity)); + } + + .sm\:divide-pink-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--divide-opacity)); + } + + .sm\:divide-pink-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--divide-opacity)); + } + + .sm\:divide-pink-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--divide-opacity)); + } + + .sm\:divide-pink-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--divide-opacity)); + } + + .sm\:divide-pink-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--divide-opacity)); + } + + .sm\:divide-solid > :not(template) ~ :not(template) { + border-style: solid; + } + + .sm\:divide-dashed > :not(template) ~ :not(template) { + border-style: dashed; + } + + .sm\:divide-dotted > :not(template) ~ :not(template) { + border-style: dotted; + } + + .sm\:divide-double > :not(template) ~ :not(template) { + border-style: double; + } + + .sm\:divide-none > :not(template) ~ :not(template) { + border-style: none; + } + + .sm\:divide-opacity-0 > :not(template) ~ :not(template) { + --divide-opacity: 0; + } + + .sm\:divide-opacity-25 > :not(template) ~ :not(template) { + --divide-opacity: 0.25; + } + + .sm\:divide-opacity-50 > :not(template) ~ :not(template) { + --divide-opacity: 0.5; + } + + .sm\:divide-opacity-75 > :not(template) ~ :not(template) { + --divide-opacity: 0.75; + } + + .sm\:divide-opacity-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + } + + .sm\:sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .sm\:not-sr-only { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .sm\:focus\:sr-only:focus { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .sm\:focus\:not-sr-only:focus { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .sm\:appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + .sm\:bg-fixed { + background-attachment: fixed; + } + + .sm\:bg-local { + background-attachment: local; + } + + .sm\:bg-scroll { + background-attachment: scroll; + } + + .sm\:bg-clip-border { + background-clip: border-box; + } + + .sm\:bg-clip-padding { + background-clip: padding-box; + } + + .sm\:bg-clip-content { + background-clip: content-box; + } + + .sm\:bg-clip-text { + -webkit-background-clip: text; + background-clip: text; + } + + .sm\:bg-transparent { + background-color: transparent; + } + + .sm\:bg-current { + background-color: currentColor; + } + + .sm\:bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .sm\:bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .sm\:bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .sm\:bg-gray-200 { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .sm\:bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .sm\:bg-gray-400 { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .sm\:bg-gray-500 { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .sm\:bg-gray-600 { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .sm\:bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .sm\:bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .sm\:bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .sm\:bg-red-100 { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .sm\:bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .sm\:bg-red-300 { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .sm\:bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .sm\:bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .sm\:bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .sm\:bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .sm\:bg-red-800 { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .sm\:bg-red-900 { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .sm\:bg-orange-100 { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .sm\:bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .sm\:bg-orange-300 { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .sm\:bg-orange-400 { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .sm\:bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .sm\:bg-orange-600 { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .sm\:bg-orange-700 { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .sm\:bg-orange-800 { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .sm\:bg-orange-900 { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .sm\:bg-yellow-100 { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .sm\:bg-yellow-200 { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .sm\:bg-yellow-300 { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .sm\:bg-yellow-400 { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .sm\:bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .sm\:bg-yellow-600 { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .sm\:bg-yellow-700 { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .sm\:bg-yellow-800 { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .sm\:bg-yellow-900 { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .sm\:bg-green-100 { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .sm\:bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .sm\:bg-green-300 { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .sm\:bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .sm\:bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .sm\:bg-green-600 { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .sm\:bg-green-700 { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .sm\:bg-green-800 { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .sm\:bg-green-900 { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .sm\:bg-teal-100 { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .sm\:bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .sm\:bg-teal-300 { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .sm\:bg-teal-400 { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .sm\:bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .sm\:bg-teal-600 { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .sm\:bg-teal-700 { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .sm\:bg-teal-800 { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .sm\:bg-teal-900 { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .sm\:bg-blue-100 { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .sm\:bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .sm\:bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .sm\:bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .sm\:bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .sm\:bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .sm\:bg-blue-700 { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .sm\:bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .sm\:bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .sm\:bg-indigo-100 { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .sm\:bg-indigo-200 { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .sm\:bg-indigo-300 { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .sm\:bg-indigo-400 { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .sm\:bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .sm\:bg-indigo-600 { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .sm\:bg-indigo-700 { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .sm\:bg-indigo-800 { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .sm\:bg-indigo-900 { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .sm\:bg-purple-100 { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .sm\:bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .sm\:bg-purple-300 { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .sm\:bg-purple-400 { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .sm\:bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .sm\:bg-purple-600 { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .sm\:bg-purple-700 { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .sm\:bg-purple-800 { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .sm\:bg-purple-900 { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .sm\:bg-pink-100 { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .sm\:bg-pink-200 { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .sm\:bg-pink-300 { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .sm\:bg-pink-400 { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .sm\:bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .sm\:bg-pink-600 { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .sm\:bg-pink-700 { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .sm\:bg-pink-800 { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .sm\:bg-pink-900 { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .sm\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .sm\:hover\:bg-current:hover { + background-color: currentColor; + } + + .sm\:hover\:bg-black:hover { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .sm\:hover\:bg-white:hover { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-100:hover { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-200:hover { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-300:hover { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-400:hover { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-500:hover { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-600:hover { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-700:hover { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-800:hover { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .sm\:hover\:bg-gray-900:hover { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-100:hover { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-200:hover { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-300:hover { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-400:hover { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-500:hover { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-600:hover { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-700:hover { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-800:hover { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .sm\:hover\:bg-red-900:hover { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-100:hover { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-200:hover { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-300:hover { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-400:hover { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-500:hover { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-600:hover { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-700:hover { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-800:hover { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .sm\:hover\:bg-orange-900:hover { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-100:hover { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-200:hover { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-300:hover { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-400:hover { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-500:hover { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-600:hover { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-700:hover { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-800:hover { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .sm\:hover\:bg-yellow-900:hover { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-100:hover { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-200:hover { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-300:hover { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-400:hover { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-500:hover { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-600:hover { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-700:hover { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-800:hover { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .sm\:hover\:bg-green-900:hover { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-100:hover { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-200:hover { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-300:hover { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-400:hover { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-500:hover { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-600:hover { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-700:hover { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-800:hover { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .sm\:hover\:bg-teal-900:hover { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-100:hover { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-200:hover { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-300:hover { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-400:hover { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-500:hover { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-600:hover { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-700:hover { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-800:hover { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .sm\:hover\:bg-blue-900:hover { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-100:hover { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-200:hover { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-300:hover { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-400:hover { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-500:hover { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-600:hover { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-700:hover { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-800:hover { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .sm\:hover\:bg-indigo-900:hover { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-100:hover { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-200:hover { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-300:hover { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-400:hover { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-500:hover { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-600:hover { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-700:hover { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-800:hover { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .sm\:hover\:bg-purple-900:hover { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-100:hover { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-200:hover { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-300:hover { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-400:hover { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-500:hover { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-600:hover { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-700:hover { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-800:hover { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .sm\:hover\:bg-pink-900:hover { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .sm\:focus\:bg-transparent:focus { + background-color: transparent; + } + + .sm\:focus\:bg-current:focus { + background-color: currentColor; + } + + .sm\:focus\:bg-black:focus { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .sm\:focus\:bg-white:focus { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-100:focus { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-200:focus { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-300:focus { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-400:focus { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-500:focus { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-600:focus { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-700:focus { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-800:focus { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .sm\:focus\:bg-gray-900:focus { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-100:focus { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-200:focus { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-300:focus { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-400:focus { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-500:focus { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-600:focus { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-700:focus { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-800:focus { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .sm\:focus\:bg-red-900:focus { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-100:focus { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-200:focus { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-300:focus { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-400:focus { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-500:focus { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-600:focus { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-700:focus { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-800:focus { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .sm\:focus\:bg-orange-900:focus { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-100:focus { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-200:focus { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-300:focus { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-400:focus { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-500:focus { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-600:focus { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-700:focus { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-800:focus { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .sm\:focus\:bg-yellow-900:focus { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-100:focus { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-200:focus { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-300:focus { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-400:focus { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-500:focus { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-600:focus { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-700:focus { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-800:focus { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .sm\:focus\:bg-green-900:focus { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-100:focus { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-200:focus { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-300:focus { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-400:focus { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-500:focus { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-600:focus { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-700:focus { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-800:focus { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .sm\:focus\:bg-teal-900:focus { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-100:focus { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-200:focus { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-300:focus { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-400:focus { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-500:focus { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-600:focus { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-700:focus { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-800:focus { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .sm\:focus\:bg-blue-900:focus { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-100:focus { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-200:focus { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-300:focus { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-400:focus { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-500:focus { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-600:focus { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-700:focus { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-800:focus { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .sm\:focus\:bg-indigo-900:focus { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-100:focus { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-200:focus { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-300:focus { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-400:focus { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-500:focus { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-600:focus { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-700:focus { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-800:focus { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .sm\:focus\:bg-purple-900:focus { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-100:focus { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-200:focus { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-300:focus { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-400:focus { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-500:focus { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-600:focus { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-700:focus { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-800:focus { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .sm\:focus\:bg-pink-900:focus { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .sm\:bg-none { + background-image: none; + } + + .sm\:bg-gradient-to-t { + background-image: linear-gradient(to top, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-r { + background-image: linear-gradient(to right, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-l { + background-image: linear-gradient(to left, var(--gradient-color-stops)); + } + + .sm\:bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--gradient-color-stops)); + } + + .sm\:from-transparent { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:from-current { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:from-black { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:from-white { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:from-gray-100 { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:from-gray-200 { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:from-gray-300 { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:from-gray-400 { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:from-gray-500 { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:from-gray-600 { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:from-gray-700 { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:from-gray-800 { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:from-gray-900 { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:from-red-100 { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:from-red-200 { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:from-red-300 { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:from-red-400 { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:from-red-500 { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:from-red-600 { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:from-red-700 { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:from-red-800 { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:from-red-900 { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:from-orange-100 { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:from-orange-200 { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:from-orange-300 { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:from-orange-400 { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:from-orange-500 { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:from-orange-600 { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:from-orange-700 { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:from-orange-800 { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:from-orange-900 { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:from-yellow-100 { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:from-yellow-200 { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:from-yellow-300 { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:from-yellow-400 { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:from-yellow-500 { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:from-yellow-600 { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:from-yellow-700 { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:from-yellow-800 { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:from-yellow-900 { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:from-green-100 { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:from-green-200 { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:from-green-300 { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:from-green-400 { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:from-green-500 { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:from-green-600 { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:from-green-700 { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:from-green-800 { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:from-green-900 { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:from-teal-100 { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:from-teal-200 { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:from-teal-300 { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:from-teal-400 { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:from-teal-500 { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:from-teal-600 { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:from-teal-700 { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:from-teal-800 { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:from-teal-900 { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:from-blue-100 { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:from-blue-200 { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:from-blue-300 { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:from-blue-400 { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:from-blue-500 { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:from-blue-600 { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:from-blue-700 { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:from-blue-800 { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:from-blue-900 { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:from-indigo-100 { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:from-indigo-200 { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:from-indigo-300 { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:from-indigo-400 { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:from-indigo-500 { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:from-indigo-600 { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:from-indigo-700 { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:from-indigo-800 { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:from-indigo-900 { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:from-purple-100 { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:from-purple-200 { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:from-purple-300 { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:from-purple-400 { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:from-purple-500 { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:from-purple-600 { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:from-purple-700 { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:from-purple-800 { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:from-purple-900 { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:from-pink-100 { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:from-pink-200 { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:from-pink-300 { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:from-pink-400 { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:from-pink-500 { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:from-pink-600 { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:from-pink-700 { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:from-pink-800 { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:from-pink-900 { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:via-transparent { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:via-current { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:via-black { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:via-white { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:via-gray-100 { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:via-gray-200 { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:via-gray-300 { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:via-gray-400 { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:via-gray-500 { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:via-gray-600 { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:via-gray-700 { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:via-gray-800 { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:via-gray-900 { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:via-red-100 { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:via-red-200 { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:via-red-300 { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:via-red-400 { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:via-red-500 { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:via-red-600 { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:via-red-700 { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:via-red-800 { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:via-red-900 { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:via-orange-100 { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:via-orange-200 { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:via-orange-300 { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:via-orange-400 { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:via-orange-500 { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:via-orange-600 { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:via-orange-700 { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:via-orange-800 { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:via-orange-900 { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:via-yellow-100 { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:via-yellow-200 { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:via-yellow-300 { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:via-yellow-400 { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:via-yellow-500 { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:via-yellow-600 { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:via-yellow-700 { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:via-yellow-800 { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:via-yellow-900 { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:via-green-100 { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:via-green-200 { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:via-green-300 { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:via-green-400 { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:via-green-500 { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:via-green-600 { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:via-green-700 { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:via-green-800 { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:via-green-900 { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:via-teal-100 { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:via-teal-200 { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:via-teal-300 { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:via-teal-400 { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:via-teal-500 { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:via-teal-600 { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:via-teal-700 { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:via-teal-800 { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:via-teal-900 { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:via-blue-100 { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:via-blue-200 { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:via-blue-300 { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:via-blue-400 { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:via-blue-500 { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:via-blue-600 { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:via-blue-700 { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:via-blue-800 { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:via-blue-900 { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:via-indigo-100 { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:via-indigo-200 { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:via-indigo-300 { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:via-indigo-400 { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:via-indigo-500 { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:via-indigo-600 { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:via-indigo-700 { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:via-indigo-800 { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:via-indigo-900 { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:via-purple-100 { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:via-purple-200 { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:via-purple-300 { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:via-purple-400 { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:via-purple-500 { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:via-purple-600 { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:via-purple-700 { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:via-purple-800 { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:via-purple-900 { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:via-pink-100 { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:via-pink-200 { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:via-pink-300 { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:via-pink-400 { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:via-pink-500 { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:via-pink-600 { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:via-pink-700 { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:via-pink-800 { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:via-pink-900 { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:to-transparent { + --gradient-to-color: transparent; + } + + .sm\:to-current { + --gradient-to-color: currentColor; + } + + .sm\:to-black { + --gradient-to-color: #000; + } + + .sm\:to-white { + --gradient-to-color: #fff; + } + + .sm\:to-gray-100 { + --gradient-to-color: #f7fafc; + } + + .sm\:to-gray-200 { + --gradient-to-color: #edf2f7; + } + + .sm\:to-gray-300 { + --gradient-to-color: #e2e8f0; + } + + .sm\:to-gray-400 { + --gradient-to-color: #cbd5e0; + } + + .sm\:to-gray-500 { + --gradient-to-color: #a0aec0; + } + + .sm\:to-gray-600 { + --gradient-to-color: #718096; + } + + .sm\:to-gray-700 { + --gradient-to-color: #4a5568; + } + + .sm\:to-gray-800 { + --gradient-to-color: #2d3748; + } + + .sm\:to-gray-900 { + --gradient-to-color: #1a202c; + } + + .sm\:to-red-100 { + --gradient-to-color: #fff5f5; + } + + .sm\:to-red-200 { + --gradient-to-color: #fed7d7; + } + + .sm\:to-red-300 { + --gradient-to-color: #feb2b2; + } + + .sm\:to-red-400 { + --gradient-to-color: #fc8181; + } + + .sm\:to-red-500 { + --gradient-to-color: #f56565; + } + + .sm\:to-red-600 { + --gradient-to-color: #e53e3e; + } + + .sm\:to-red-700 { + --gradient-to-color: #c53030; + } + + .sm\:to-red-800 { + --gradient-to-color: #9b2c2c; + } + + .sm\:to-red-900 { + --gradient-to-color: #742a2a; + } + + .sm\:to-orange-100 { + --gradient-to-color: #fffaf0; + } + + .sm\:to-orange-200 { + --gradient-to-color: #feebc8; + } + + .sm\:to-orange-300 { + --gradient-to-color: #fbd38d; + } + + .sm\:to-orange-400 { + --gradient-to-color: #f6ad55; + } + + .sm\:to-orange-500 { + --gradient-to-color: #ed8936; + } + + .sm\:to-orange-600 { + --gradient-to-color: #dd6b20; + } + + .sm\:to-orange-700 { + --gradient-to-color: #c05621; + } + + .sm\:to-orange-800 { + --gradient-to-color: #9c4221; + } + + .sm\:to-orange-900 { + --gradient-to-color: #7b341e; + } + + .sm\:to-yellow-100 { + --gradient-to-color: #fffff0; + } + + .sm\:to-yellow-200 { + --gradient-to-color: #fefcbf; + } + + .sm\:to-yellow-300 { + --gradient-to-color: #faf089; + } + + .sm\:to-yellow-400 { + --gradient-to-color: #f6e05e; + } + + .sm\:to-yellow-500 { + --gradient-to-color: #ecc94b; + } + + .sm\:to-yellow-600 { + --gradient-to-color: #d69e2e; + } + + .sm\:to-yellow-700 { + --gradient-to-color: #b7791f; + } + + .sm\:to-yellow-800 { + --gradient-to-color: #975a16; + } + + .sm\:to-yellow-900 { + --gradient-to-color: #744210; + } + + .sm\:to-green-100 { + --gradient-to-color: #f0fff4; + } + + .sm\:to-green-200 { + --gradient-to-color: #c6f6d5; + } + + .sm\:to-green-300 { + --gradient-to-color: #9ae6b4; + } + + .sm\:to-green-400 { + --gradient-to-color: #68d391; + } + + .sm\:to-green-500 { + --gradient-to-color: #48bb78; + } + + .sm\:to-green-600 { + --gradient-to-color: #38a169; + } + + .sm\:to-green-700 { + --gradient-to-color: #2f855a; + } + + .sm\:to-green-800 { + --gradient-to-color: #276749; + } + + .sm\:to-green-900 { + --gradient-to-color: #22543d; + } + + .sm\:to-teal-100 { + --gradient-to-color: #e6fffa; + } + + .sm\:to-teal-200 { + --gradient-to-color: #b2f5ea; + } + + .sm\:to-teal-300 { + --gradient-to-color: #81e6d9; + } + + .sm\:to-teal-400 { + --gradient-to-color: #4fd1c5; + } + + .sm\:to-teal-500 { + --gradient-to-color: #38b2ac; + } + + .sm\:to-teal-600 { + --gradient-to-color: #319795; + } + + .sm\:to-teal-700 { + --gradient-to-color: #2c7a7b; + } + + .sm\:to-teal-800 { + --gradient-to-color: #285e61; + } + + .sm\:to-teal-900 { + --gradient-to-color: #234e52; + } + + .sm\:to-blue-100 { + --gradient-to-color: #ebf8ff; + } + + .sm\:to-blue-200 { + --gradient-to-color: #bee3f8; + } + + .sm\:to-blue-300 { + --gradient-to-color: #90cdf4; + } + + .sm\:to-blue-400 { + --gradient-to-color: #63b3ed; + } + + .sm\:to-blue-500 { + --gradient-to-color: #4299e1; + } + + .sm\:to-blue-600 { + --gradient-to-color: #3182ce; + } + + .sm\:to-blue-700 { + --gradient-to-color: #2b6cb0; + } + + .sm\:to-blue-800 { + --gradient-to-color: #2c5282; + } + + .sm\:to-blue-900 { + --gradient-to-color: #2a4365; + } + + .sm\:to-indigo-100 { + --gradient-to-color: #ebf4ff; + } + + .sm\:to-indigo-200 { + --gradient-to-color: #c3dafe; + } + + .sm\:to-indigo-300 { + --gradient-to-color: #a3bffa; + } + + .sm\:to-indigo-400 { + --gradient-to-color: #7f9cf5; + } + + .sm\:to-indigo-500 { + --gradient-to-color: #667eea; + } + + .sm\:to-indigo-600 { + --gradient-to-color: #5a67d8; + } + + .sm\:to-indigo-700 { + --gradient-to-color: #4c51bf; + } + + .sm\:to-indigo-800 { + --gradient-to-color: #434190; + } + + .sm\:to-indigo-900 { + --gradient-to-color: #3c366b; + } + + .sm\:to-purple-100 { + --gradient-to-color: #faf5ff; + } + + .sm\:to-purple-200 { + --gradient-to-color: #e9d8fd; + } + + .sm\:to-purple-300 { + --gradient-to-color: #d6bcfa; + } + + .sm\:to-purple-400 { + --gradient-to-color: #b794f4; + } + + .sm\:to-purple-500 { + --gradient-to-color: #9f7aea; + } + + .sm\:to-purple-600 { + --gradient-to-color: #805ad5; + } + + .sm\:to-purple-700 { + --gradient-to-color: #6b46c1; + } + + .sm\:to-purple-800 { + --gradient-to-color: #553c9a; + } + + .sm\:to-purple-900 { + --gradient-to-color: #44337a; + } + + .sm\:to-pink-100 { + --gradient-to-color: #fff5f7; + } + + .sm\:to-pink-200 { + --gradient-to-color: #fed7e2; + } + + .sm\:to-pink-300 { + --gradient-to-color: #fbb6ce; + } + + .sm\:to-pink-400 { + --gradient-to-color: #f687b3; + } + + .sm\:to-pink-500 { + --gradient-to-color: #ed64a6; + } + + .sm\:to-pink-600 { + --gradient-to-color: #d53f8c; + } + + .sm\:to-pink-700 { + --gradient-to-color: #b83280; + } + + .sm\:to-pink-800 { + --gradient-to-color: #97266d; + } + + .sm\:to-pink-900 { + --gradient-to-color: #702459; + } + + .sm\:hover\:from-transparent:hover { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:hover\:from-current:hover { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:hover\:from-black:hover { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:hover\:from-white:hover { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:hover\:from-gray-100:hover { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:hover\:from-gray-200:hover { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:hover\:from-gray-300:hover { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:hover\:from-gray-400:hover { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:hover\:from-gray-500:hover { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:hover\:from-gray-600:hover { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:hover\:from-gray-700:hover { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:hover\:from-gray-800:hover { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:hover\:from-gray-900:hover { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:hover\:from-red-100:hover { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:hover\:from-red-200:hover { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:hover\:from-red-300:hover { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:hover\:from-red-400:hover { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:hover\:from-red-500:hover { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:hover\:from-red-600:hover { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:hover\:from-red-700:hover { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:hover\:from-red-800:hover { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:hover\:from-red-900:hover { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:hover\:from-orange-100:hover { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:hover\:from-orange-200:hover { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:hover\:from-orange-300:hover { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:hover\:from-orange-400:hover { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:hover\:from-orange-500:hover { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:hover\:from-orange-600:hover { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:hover\:from-orange-700:hover { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:hover\:from-orange-800:hover { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:hover\:from-orange-900:hover { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:hover\:from-yellow-100:hover { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:hover\:from-yellow-200:hover { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:hover\:from-yellow-300:hover { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:hover\:from-yellow-400:hover { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:hover\:from-yellow-500:hover { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:hover\:from-yellow-600:hover { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:hover\:from-yellow-700:hover { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:hover\:from-yellow-800:hover { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:hover\:from-yellow-900:hover { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:hover\:from-green-100:hover { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:hover\:from-green-200:hover { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:hover\:from-green-300:hover { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:hover\:from-green-400:hover { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:hover\:from-green-500:hover { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:hover\:from-green-600:hover { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:hover\:from-green-700:hover { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:hover\:from-green-800:hover { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:hover\:from-green-900:hover { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:hover\:from-teal-100:hover { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:hover\:from-teal-200:hover { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:hover\:from-teal-300:hover { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:hover\:from-teal-400:hover { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:hover\:from-teal-500:hover { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:hover\:from-teal-600:hover { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:hover\:from-teal-700:hover { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:hover\:from-teal-800:hover { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:hover\:from-teal-900:hover { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:hover\:from-blue-100:hover { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:hover\:from-blue-200:hover { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:hover\:from-blue-300:hover { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:hover\:from-blue-400:hover { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:hover\:from-blue-500:hover { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:hover\:from-blue-600:hover { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:hover\:from-blue-700:hover { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:hover\:from-blue-800:hover { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:hover\:from-blue-900:hover { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:hover\:from-indigo-100:hover { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:hover\:from-indigo-200:hover { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:hover\:from-indigo-300:hover { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:hover\:from-indigo-400:hover { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:hover\:from-indigo-500:hover { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:hover\:from-indigo-600:hover { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:hover\:from-indigo-700:hover { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:hover\:from-indigo-800:hover { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:hover\:from-indigo-900:hover { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:hover\:from-purple-100:hover { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:hover\:from-purple-200:hover { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:hover\:from-purple-300:hover { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:hover\:from-purple-400:hover { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:hover\:from-purple-500:hover { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:hover\:from-purple-600:hover { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:hover\:from-purple-700:hover { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:hover\:from-purple-800:hover { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:hover\:from-purple-900:hover { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:hover\:from-pink-100:hover { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:hover\:from-pink-200:hover { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:hover\:from-pink-300:hover { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:hover\:from-pink-400:hover { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:hover\:from-pink-500:hover { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:hover\:from-pink-600:hover { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:hover\:from-pink-700:hover { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:hover\:from-pink-800:hover { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:hover\:from-pink-900:hover { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:hover\:via-transparent:hover { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:hover\:via-current:hover { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:hover\:via-black:hover { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:hover\:via-white:hover { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:hover\:via-gray-100:hover { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:hover\:via-gray-200:hover { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:hover\:via-gray-300:hover { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:hover\:via-gray-400:hover { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:hover\:via-gray-500:hover { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:hover\:via-gray-600:hover { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:hover\:via-gray-700:hover { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:hover\:via-gray-800:hover { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:hover\:via-gray-900:hover { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:hover\:via-red-100:hover { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:hover\:via-red-200:hover { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:hover\:via-red-300:hover { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:hover\:via-red-400:hover { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:hover\:via-red-500:hover { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:hover\:via-red-600:hover { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:hover\:via-red-700:hover { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:hover\:via-red-800:hover { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:hover\:via-red-900:hover { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:hover\:via-orange-100:hover { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:hover\:via-orange-200:hover { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:hover\:via-orange-300:hover { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:hover\:via-orange-400:hover { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:hover\:via-orange-500:hover { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:hover\:via-orange-600:hover { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:hover\:via-orange-700:hover { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:hover\:via-orange-800:hover { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:hover\:via-orange-900:hover { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:hover\:via-yellow-100:hover { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:hover\:via-yellow-200:hover { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:hover\:via-yellow-300:hover { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:hover\:via-yellow-400:hover { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:hover\:via-yellow-500:hover { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:hover\:via-yellow-600:hover { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:hover\:via-yellow-700:hover { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:hover\:via-yellow-800:hover { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:hover\:via-yellow-900:hover { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:hover\:via-green-100:hover { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:hover\:via-green-200:hover { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:hover\:via-green-300:hover { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:hover\:via-green-400:hover { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:hover\:via-green-500:hover { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:hover\:via-green-600:hover { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:hover\:via-green-700:hover { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:hover\:via-green-800:hover { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:hover\:via-green-900:hover { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:hover\:via-teal-100:hover { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:hover\:via-teal-200:hover { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:hover\:via-teal-300:hover { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:hover\:via-teal-400:hover { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:hover\:via-teal-500:hover { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:hover\:via-teal-600:hover { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:hover\:via-teal-700:hover { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:hover\:via-teal-800:hover { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:hover\:via-teal-900:hover { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:hover\:via-blue-100:hover { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:hover\:via-blue-200:hover { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:hover\:via-blue-300:hover { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:hover\:via-blue-400:hover { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:hover\:via-blue-500:hover { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:hover\:via-blue-600:hover { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:hover\:via-blue-700:hover { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:hover\:via-blue-800:hover { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:hover\:via-blue-900:hover { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:hover\:via-indigo-100:hover { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:hover\:via-indigo-200:hover { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:hover\:via-indigo-300:hover { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:hover\:via-indigo-400:hover { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:hover\:via-indigo-500:hover { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:hover\:via-indigo-600:hover { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:hover\:via-indigo-700:hover { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:hover\:via-indigo-800:hover { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:hover\:via-indigo-900:hover { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:hover\:via-purple-100:hover { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:hover\:via-purple-200:hover { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:hover\:via-purple-300:hover { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:hover\:via-purple-400:hover { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:hover\:via-purple-500:hover { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:hover\:via-purple-600:hover { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:hover\:via-purple-700:hover { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:hover\:via-purple-800:hover { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:hover\:via-purple-900:hover { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:hover\:via-pink-100:hover { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:hover\:via-pink-200:hover { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:hover\:via-pink-300:hover { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:hover\:via-pink-400:hover { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:hover\:via-pink-500:hover { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:hover\:via-pink-600:hover { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:hover\:via-pink-700:hover { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:hover\:via-pink-800:hover { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:hover\:via-pink-900:hover { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:hover\:to-transparent:hover { + --gradient-to-color: transparent; + } + + .sm\:hover\:to-current:hover { + --gradient-to-color: currentColor; + } + + .sm\:hover\:to-black:hover { + --gradient-to-color: #000; + } + + .sm\:hover\:to-white:hover { + --gradient-to-color: #fff; + } + + .sm\:hover\:to-gray-100:hover { + --gradient-to-color: #f7fafc; + } + + .sm\:hover\:to-gray-200:hover { + --gradient-to-color: #edf2f7; + } + + .sm\:hover\:to-gray-300:hover { + --gradient-to-color: #e2e8f0; + } + + .sm\:hover\:to-gray-400:hover { + --gradient-to-color: #cbd5e0; + } + + .sm\:hover\:to-gray-500:hover { + --gradient-to-color: #a0aec0; + } + + .sm\:hover\:to-gray-600:hover { + --gradient-to-color: #718096; + } + + .sm\:hover\:to-gray-700:hover { + --gradient-to-color: #4a5568; + } + + .sm\:hover\:to-gray-800:hover { + --gradient-to-color: #2d3748; + } + + .sm\:hover\:to-gray-900:hover { + --gradient-to-color: #1a202c; + } + + .sm\:hover\:to-red-100:hover { + --gradient-to-color: #fff5f5; + } + + .sm\:hover\:to-red-200:hover { + --gradient-to-color: #fed7d7; + } + + .sm\:hover\:to-red-300:hover { + --gradient-to-color: #feb2b2; + } + + .sm\:hover\:to-red-400:hover { + --gradient-to-color: #fc8181; + } + + .sm\:hover\:to-red-500:hover { + --gradient-to-color: #f56565; + } + + .sm\:hover\:to-red-600:hover { + --gradient-to-color: #e53e3e; + } + + .sm\:hover\:to-red-700:hover { + --gradient-to-color: #c53030; + } + + .sm\:hover\:to-red-800:hover { + --gradient-to-color: #9b2c2c; + } + + .sm\:hover\:to-red-900:hover { + --gradient-to-color: #742a2a; + } + + .sm\:hover\:to-orange-100:hover { + --gradient-to-color: #fffaf0; + } + + .sm\:hover\:to-orange-200:hover { + --gradient-to-color: #feebc8; + } + + .sm\:hover\:to-orange-300:hover { + --gradient-to-color: #fbd38d; + } + + .sm\:hover\:to-orange-400:hover { + --gradient-to-color: #f6ad55; + } + + .sm\:hover\:to-orange-500:hover { + --gradient-to-color: #ed8936; + } + + .sm\:hover\:to-orange-600:hover { + --gradient-to-color: #dd6b20; + } + + .sm\:hover\:to-orange-700:hover { + --gradient-to-color: #c05621; + } + + .sm\:hover\:to-orange-800:hover { + --gradient-to-color: #9c4221; + } + + .sm\:hover\:to-orange-900:hover { + --gradient-to-color: #7b341e; + } + + .sm\:hover\:to-yellow-100:hover { + --gradient-to-color: #fffff0; + } + + .sm\:hover\:to-yellow-200:hover { + --gradient-to-color: #fefcbf; + } + + .sm\:hover\:to-yellow-300:hover { + --gradient-to-color: #faf089; + } + + .sm\:hover\:to-yellow-400:hover { + --gradient-to-color: #f6e05e; + } + + .sm\:hover\:to-yellow-500:hover { + --gradient-to-color: #ecc94b; + } + + .sm\:hover\:to-yellow-600:hover { + --gradient-to-color: #d69e2e; + } + + .sm\:hover\:to-yellow-700:hover { + --gradient-to-color: #b7791f; + } + + .sm\:hover\:to-yellow-800:hover { + --gradient-to-color: #975a16; + } + + .sm\:hover\:to-yellow-900:hover { + --gradient-to-color: #744210; + } + + .sm\:hover\:to-green-100:hover { + --gradient-to-color: #f0fff4; + } + + .sm\:hover\:to-green-200:hover { + --gradient-to-color: #c6f6d5; + } + + .sm\:hover\:to-green-300:hover { + --gradient-to-color: #9ae6b4; + } + + .sm\:hover\:to-green-400:hover { + --gradient-to-color: #68d391; + } + + .sm\:hover\:to-green-500:hover { + --gradient-to-color: #48bb78; + } + + .sm\:hover\:to-green-600:hover { + --gradient-to-color: #38a169; + } + + .sm\:hover\:to-green-700:hover { + --gradient-to-color: #2f855a; + } + + .sm\:hover\:to-green-800:hover { + --gradient-to-color: #276749; + } + + .sm\:hover\:to-green-900:hover { + --gradient-to-color: #22543d; + } + + .sm\:hover\:to-teal-100:hover { + --gradient-to-color: #e6fffa; + } + + .sm\:hover\:to-teal-200:hover { + --gradient-to-color: #b2f5ea; + } + + .sm\:hover\:to-teal-300:hover { + --gradient-to-color: #81e6d9; + } + + .sm\:hover\:to-teal-400:hover { + --gradient-to-color: #4fd1c5; + } + + .sm\:hover\:to-teal-500:hover { + --gradient-to-color: #38b2ac; + } + + .sm\:hover\:to-teal-600:hover { + --gradient-to-color: #319795; + } + + .sm\:hover\:to-teal-700:hover { + --gradient-to-color: #2c7a7b; + } + + .sm\:hover\:to-teal-800:hover { + --gradient-to-color: #285e61; + } + + .sm\:hover\:to-teal-900:hover { + --gradient-to-color: #234e52; + } + + .sm\:hover\:to-blue-100:hover { + --gradient-to-color: #ebf8ff; + } + + .sm\:hover\:to-blue-200:hover { + --gradient-to-color: #bee3f8; + } + + .sm\:hover\:to-blue-300:hover { + --gradient-to-color: #90cdf4; + } + + .sm\:hover\:to-blue-400:hover { + --gradient-to-color: #63b3ed; + } + + .sm\:hover\:to-blue-500:hover { + --gradient-to-color: #4299e1; + } + + .sm\:hover\:to-blue-600:hover { + --gradient-to-color: #3182ce; + } + + .sm\:hover\:to-blue-700:hover { + --gradient-to-color: #2b6cb0; + } + + .sm\:hover\:to-blue-800:hover { + --gradient-to-color: #2c5282; + } + + .sm\:hover\:to-blue-900:hover { + --gradient-to-color: #2a4365; + } + + .sm\:hover\:to-indigo-100:hover { + --gradient-to-color: #ebf4ff; + } + + .sm\:hover\:to-indigo-200:hover { + --gradient-to-color: #c3dafe; + } + + .sm\:hover\:to-indigo-300:hover { + --gradient-to-color: #a3bffa; + } + + .sm\:hover\:to-indigo-400:hover { + --gradient-to-color: #7f9cf5; + } + + .sm\:hover\:to-indigo-500:hover { + --gradient-to-color: #667eea; + } + + .sm\:hover\:to-indigo-600:hover { + --gradient-to-color: #5a67d8; + } + + .sm\:hover\:to-indigo-700:hover { + --gradient-to-color: #4c51bf; + } + + .sm\:hover\:to-indigo-800:hover { + --gradient-to-color: #434190; + } + + .sm\:hover\:to-indigo-900:hover { + --gradient-to-color: #3c366b; + } + + .sm\:hover\:to-purple-100:hover { + --gradient-to-color: #faf5ff; + } + + .sm\:hover\:to-purple-200:hover { + --gradient-to-color: #e9d8fd; + } + + .sm\:hover\:to-purple-300:hover { + --gradient-to-color: #d6bcfa; + } + + .sm\:hover\:to-purple-400:hover { + --gradient-to-color: #b794f4; + } + + .sm\:hover\:to-purple-500:hover { + --gradient-to-color: #9f7aea; + } + + .sm\:hover\:to-purple-600:hover { + --gradient-to-color: #805ad5; + } + + .sm\:hover\:to-purple-700:hover { + --gradient-to-color: #6b46c1; + } + + .sm\:hover\:to-purple-800:hover { + --gradient-to-color: #553c9a; + } + + .sm\:hover\:to-purple-900:hover { + --gradient-to-color: #44337a; + } + + .sm\:hover\:to-pink-100:hover { + --gradient-to-color: #fff5f7; + } + + .sm\:hover\:to-pink-200:hover { + --gradient-to-color: #fed7e2; + } + + .sm\:hover\:to-pink-300:hover { + --gradient-to-color: #fbb6ce; + } + + .sm\:hover\:to-pink-400:hover { + --gradient-to-color: #f687b3; + } + + .sm\:hover\:to-pink-500:hover { + --gradient-to-color: #ed64a6; + } + + .sm\:hover\:to-pink-600:hover { + --gradient-to-color: #d53f8c; + } + + .sm\:hover\:to-pink-700:hover { + --gradient-to-color: #b83280; + } + + .sm\:hover\:to-pink-800:hover { + --gradient-to-color: #97266d; + } + + .sm\:hover\:to-pink-900:hover { + --gradient-to-color: #702459; + } + + .sm\:focus\:from-transparent:focus { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:focus\:from-current:focus { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:focus\:from-black:focus { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:focus\:from-white:focus { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:focus\:from-gray-100:focus { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:focus\:from-gray-200:focus { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:focus\:from-gray-300:focus { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:focus\:from-gray-400:focus { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:focus\:from-gray-500:focus { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:focus\:from-gray-600:focus { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:focus\:from-gray-700:focus { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:focus\:from-gray-800:focus { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:focus\:from-gray-900:focus { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:focus\:from-red-100:focus { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:focus\:from-red-200:focus { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:focus\:from-red-300:focus { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:focus\:from-red-400:focus { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:focus\:from-red-500:focus { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:focus\:from-red-600:focus { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:focus\:from-red-700:focus { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:focus\:from-red-800:focus { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:focus\:from-red-900:focus { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:focus\:from-orange-100:focus { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:focus\:from-orange-200:focus { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:focus\:from-orange-300:focus { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:focus\:from-orange-400:focus { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:focus\:from-orange-500:focus { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:focus\:from-orange-600:focus { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:focus\:from-orange-700:focus { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:focus\:from-orange-800:focus { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:focus\:from-orange-900:focus { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:focus\:from-yellow-100:focus { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:focus\:from-yellow-200:focus { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:focus\:from-yellow-300:focus { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:focus\:from-yellow-400:focus { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:focus\:from-yellow-500:focus { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:focus\:from-yellow-600:focus { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:focus\:from-yellow-700:focus { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:focus\:from-yellow-800:focus { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:focus\:from-yellow-900:focus { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:focus\:from-green-100:focus { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:focus\:from-green-200:focus { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:focus\:from-green-300:focus { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:focus\:from-green-400:focus { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:focus\:from-green-500:focus { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:focus\:from-green-600:focus { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:focus\:from-green-700:focus { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:focus\:from-green-800:focus { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:focus\:from-green-900:focus { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:focus\:from-teal-100:focus { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:focus\:from-teal-200:focus { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:focus\:from-teal-300:focus { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:focus\:from-teal-400:focus { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:focus\:from-teal-500:focus { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:focus\:from-teal-600:focus { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:focus\:from-teal-700:focus { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:focus\:from-teal-800:focus { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:focus\:from-teal-900:focus { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:focus\:from-blue-100:focus { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:focus\:from-blue-200:focus { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:focus\:from-blue-300:focus { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:focus\:from-blue-400:focus { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:focus\:from-blue-500:focus { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:focus\:from-blue-600:focus { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:focus\:from-blue-700:focus { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:focus\:from-blue-800:focus { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:focus\:from-blue-900:focus { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:focus\:from-indigo-100:focus { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:focus\:from-indigo-200:focus { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:focus\:from-indigo-300:focus { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:focus\:from-indigo-400:focus { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:focus\:from-indigo-500:focus { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:focus\:from-indigo-600:focus { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:focus\:from-indigo-700:focus { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:focus\:from-indigo-800:focus { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:focus\:from-indigo-900:focus { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:focus\:from-purple-100:focus { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:focus\:from-purple-200:focus { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:focus\:from-purple-300:focus { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:focus\:from-purple-400:focus { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:focus\:from-purple-500:focus { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:focus\:from-purple-600:focus { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:focus\:from-purple-700:focus { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:focus\:from-purple-800:focus { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:focus\:from-purple-900:focus { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:focus\:from-pink-100:focus { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:focus\:from-pink-200:focus { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:focus\:from-pink-300:focus { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:focus\:from-pink-400:focus { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:focus\:from-pink-500:focus { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:focus\:from-pink-600:focus { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:focus\:from-pink-700:focus { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:focus\:from-pink-800:focus { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:focus\:from-pink-900:focus { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:focus\:via-transparent:focus { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:focus\:via-current:focus { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:focus\:via-black:focus { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .sm\:focus\:via-white:focus { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .sm\:focus\:via-gray-100:focus { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .sm\:focus\:via-gray-200:focus { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .sm\:focus\:via-gray-300:focus { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .sm\:focus\:via-gray-400:focus { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .sm\:focus\:via-gray-500:focus { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .sm\:focus\:via-gray-600:focus { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .sm\:focus\:via-gray-700:focus { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .sm\:focus\:via-gray-800:focus { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .sm\:focus\:via-gray-900:focus { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .sm\:focus\:via-red-100:focus { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .sm\:focus\:via-red-200:focus { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .sm\:focus\:via-red-300:focus { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .sm\:focus\:via-red-400:focus { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .sm\:focus\:via-red-500:focus { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .sm\:focus\:via-red-600:focus { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .sm\:focus\:via-red-700:focus { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .sm\:focus\:via-red-800:focus { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .sm\:focus\:via-red-900:focus { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .sm\:focus\:via-orange-100:focus { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .sm\:focus\:via-orange-200:focus { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .sm\:focus\:via-orange-300:focus { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .sm\:focus\:via-orange-400:focus { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .sm\:focus\:via-orange-500:focus { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .sm\:focus\:via-orange-600:focus { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .sm\:focus\:via-orange-700:focus { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .sm\:focus\:via-orange-800:focus { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .sm\:focus\:via-orange-900:focus { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .sm\:focus\:via-yellow-100:focus { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .sm\:focus\:via-yellow-200:focus { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .sm\:focus\:via-yellow-300:focus { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .sm\:focus\:via-yellow-400:focus { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .sm\:focus\:via-yellow-500:focus { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .sm\:focus\:via-yellow-600:focus { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .sm\:focus\:via-yellow-700:focus { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .sm\:focus\:via-yellow-800:focus { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .sm\:focus\:via-yellow-900:focus { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .sm\:focus\:via-green-100:focus { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .sm\:focus\:via-green-200:focus { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .sm\:focus\:via-green-300:focus { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .sm\:focus\:via-green-400:focus { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .sm\:focus\:via-green-500:focus { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .sm\:focus\:via-green-600:focus { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .sm\:focus\:via-green-700:focus { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .sm\:focus\:via-green-800:focus { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .sm\:focus\:via-green-900:focus { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .sm\:focus\:via-teal-100:focus { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .sm\:focus\:via-teal-200:focus { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .sm\:focus\:via-teal-300:focus { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .sm\:focus\:via-teal-400:focus { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .sm\:focus\:via-teal-500:focus { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .sm\:focus\:via-teal-600:focus { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .sm\:focus\:via-teal-700:focus { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .sm\:focus\:via-teal-800:focus { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .sm\:focus\:via-teal-900:focus { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .sm\:focus\:via-blue-100:focus { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .sm\:focus\:via-blue-200:focus { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .sm\:focus\:via-blue-300:focus { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .sm\:focus\:via-blue-400:focus { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .sm\:focus\:via-blue-500:focus { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .sm\:focus\:via-blue-600:focus { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .sm\:focus\:via-blue-700:focus { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .sm\:focus\:via-blue-800:focus { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .sm\:focus\:via-blue-900:focus { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .sm\:focus\:via-indigo-100:focus { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .sm\:focus\:via-indigo-200:focus { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .sm\:focus\:via-indigo-300:focus { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .sm\:focus\:via-indigo-400:focus { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .sm\:focus\:via-indigo-500:focus { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .sm\:focus\:via-indigo-600:focus { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .sm\:focus\:via-indigo-700:focus { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .sm\:focus\:via-indigo-800:focus { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .sm\:focus\:via-indigo-900:focus { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .sm\:focus\:via-purple-100:focus { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .sm\:focus\:via-purple-200:focus { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .sm\:focus\:via-purple-300:focus { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .sm\:focus\:via-purple-400:focus { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .sm\:focus\:via-purple-500:focus { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .sm\:focus\:via-purple-600:focus { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .sm\:focus\:via-purple-700:focus { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .sm\:focus\:via-purple-800:focus { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .sm\:focus\:via-purple-900:focus { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .sm\:focus\:via-pink-100:focus { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .sm\:focus\:via-pink-200:focus { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .sm\:focus\:via-pink-300:focus { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .sm\:focus\:via-pink-400:focus { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .sm\:focus\:via-pink-500:focus { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .sm\:focus\:via-pink-600:focus { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .sm\:focus\:via-pink-700:focus { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .sm\:focus\:via-pink-800:focus { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .sm\:focus\:via-pink-900:focus { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .sm\:focus\:to-transparent:focus { + --gradient-to-color: transparent; + } + + .sm\:focus\:to-current:focus { + --gradient-to-color: currentColor; + } + + .sm\:focus\:to-black:focus { + --gradient-to-color: #000; + } + + .sm\:focus\:to-white:focus { + --gradient-to-color: #fff; + } + + .sm\:focus\:to-gray-100:focus { + --gradient-to-color: #f7fafc; + } + + .sm\:focus\:to-gray-200:focus { + --gradient-to-color: #edf2f7; + } + + .sm\:focus\:to-gray-300:focus { + --gradient-to-color: #e2e8f0; + } + + .sm\:focus\:to-gray-400:focus { + --gradient-to-color: #cbd5e0; + } + + .sm\:focus\:to-gray-500:focus { + --gradient-to-color: #a0aec0; + } + + .sm\:focus\:to-gray-600:focus { + --gradient-to-color: #718096; + } + + .sm\:focus\:to-gray-700:focus { + --gradient-to-color: #4a5568; + } + + .sm\:focus\:to-gray-800:focus { + --gradient-to-color: #2d3748; + } + + .sm\:focus\:to-gray-900:focus { + --gradient-to-color: #1a202c; + } + + .sm\:focus\:to-red-100:focus { + --gradient-to-color: #fff5f5; + } + + .sm\:focus\:to-red-200:focus { + --gradient-to-color: #fed7d7; + } + + .sm\:focus\:to-red-300:focus { + --gradient-to-color: #feb2b2; + } + + .sm\:focus\:to-red-400:focus { + --gradient-to-color: #fc8181; + } + + .sm\:focus\:to-red-500:focus { + --gradient-to-color: #f56565; + } + + .sm\:focus\:to-red-600:focus { + --gradient-to-color: #e53e3e; + } + + .sm\:focus\:to-red-700:focus { + --gradient-to-color: #c53030; + } + + .sm\:focus\:to-red-800:focus { + --gradient-to-color: #9b2c2c; + } + + .sm\:focus\:to-red-900:focus { + --gradient-to-color: #742a2a; + } + + .sm\:focus\:to-orange-100:focus { + --gradient-to-color: #fffaf0; + } + + .sm\:focus\:to-orange-200:focus { + --gradient-to-color: #feebc8; + } + + .sm\:focus\:to-orange-300:focus { + --gradient-to-color: #fbd38d; + } + + .sm\:focus\:to-orange-400:focus { + --gradient-to-color: #f6ad55; + } + + .sm\:focus\:to-orange-500:focus { + --gradient-to-color: #ed8936; + } + + .sm\:focus\:to-orange-600:focus { + --gradient-to-color: #dd6b20; + } + + .sm\:focus\:to-orange-700:focus { + --gradient-to-color: #c05621; + } + + .sm\:focus\:to-orange-800:focus { + --gradient-to-color: #9c4221; + } + + .sm\:focus\:to-orange-900:focus { + --gradient-to-color: #7b341e; + } + + .sm\:focus\:to-yellow-100:focus { + --gradient-to-color: #fffff0; + } + + .sm\:focus\:to-yellow-200:focus { + --gradient-to-color: #fefcbf; + } + + .sm\:focus\:to-yellow-300:focus { + --gradient-to-color: #faf089; + } + + .sm\:focus\:to-yellow-400:focus { + --gradient-to-color: #f6e05e; + } + + .sm\:focus\:to-yellow-500:focus { + --gradient-to-color: #ecc94b; + } + + .sm\:focus\:to-yellow-600:focus { + --gradient-to-color: #d69e2e; + } + + .sm\:focus\:to-yellow-700:focus { + --gradient-to-color: #b7791f; + } + + .sm\:focus\:to-yellow-800:focus { + --gradient-to-color: #975a16; + } + + .sm\:focus\:to-yellow-900:focus { + --gradient-to-color: #744210; + } + + .sm\:focus\:to-green-100:focus { + --gradient-to-color: #f0fff4; + } + + .sm\:focus\:to-green-200:focus { + --gradient-to-color: #c6f6d5; + } + + .sm\:focus\:to-green-300:focus { + --gradient-to-color: #9ae6b4; + } + + .sm\:focus\:to-green-400:focus { + --gradient-to-color: #68d391; + } + + .sm\:focus\:to-green-500:focus { + --gradient-to-color: #48bb78; + } + + .sm\:focus\:to-green-600:focus { + --gradient-to-color: #38a169; + } + + .sm\:focus\:to-green-700:focus { + --gradient-to-color: #2f855a; + } + + .sm\:focus\:to-green-800:focus { + --gradient-to-color: #276749; + } + + .sm\:focus\:to-green-900:focus { + --gradient-to-color: #22543d; + } + + .sm\:focus\:to-teal-100:focus { + --gradient-to-color: #e6fffa; + } + + .sm\:focus\:to-teal-200:focus { + --gradient-to-color: #b2f5ea; + } + + .sm\:focus\:to-teal-300:focus { + --gradient-to-color: #81e6d9; + } + + .sm\:focus\:to-teal-400:focus { + --gradient-to-color: #4fd1c5; + } + + .sm\:focus\:to-teal-500:focus { + --gradient-to-color: #38b2ac; + } + + .sm\:focus\:to-teal-600:focus { + --gradient-to-color: #319795; + } + + .sm\:focus\:to-teal-700:focus { + --gradient-to-color: #2c7a7b; + } + + .sm\:focus\:to-teal-800:focus { + --gradient-to-color: #285e61; + } + + .sm\:focus\:to-teal-900:focus { + --gradient-to-color: #234e52; + } + + .sm\:focus\:to-blue-100:focus { + --gradient-to-color: #ebf8ff; + } + + .sm\:focus\:to-blue-200:focus { + --gradient-to-color: #bee3f8; + } + + .sm\:focus\:to-blue-300:focus { + --gradient-to-color: #90cdf4; + } + + .sm\:focus\:to-blue-400:focus { + --gradient-to-color: #63b3ed; + } + + .sm\:focus\:to-blue-500:focus { + --gradient-to-color: #4299e1; + } + + .sm\:focus\:to-blue-600:focus { + --gradient-to-color: #3182ce; + } + + .sm\:focus\:to-blue-700:focus { + --gradient-to-color: #2b6cb0; + } + + .sm\:focus\:to-blue-800:focus { + --gradient-to-color: #2c5282; + } + + .sm\:focus\:to-blue-900:focus { + --gradient-to-color: #2a4365; + } + + .sm\:focus\:to-indigo-100:focus { + --gradient-to-color: #ebf4ff; + } + + .sm\:focus\:to-indigo-200:focus { + --gradient-to-color: #c3dafe; + } + + .sm\:focus\:to-indigo-300:focus { + --gradient-to-color: #a3bffa; + } + + .sm\:focus\:to-indigo-400:focus { + --gradient-to-color: #7f9cf5; + } + + .sm\:focus\:to-indigo-500:focus { + --gradient-to-color: #667eea; + } + + .sm\:focus\:to-indigo-600:focus { + --gradient-to-color: #5a67d8; + } + + .sm\:focus\:to-indigo-700:focus { + --gradient-to-color: #4c51bf; + } + + .sm\:focus\:to-indigo-800:focus { + --gradient-to-color: #434190; + } + + .sm\:focus\:to-indigo-900:focus { + --gradient-to-color: #3c366b; + } + + .sm\:focus\:to-purple-100:focus { + --gradient-to-color: #faf5ff; + } + + .sm\:focus\:to-purple-200:focus { + --gradient-to-color: #e9d8fd; + } + + .sm\:focus\:to-purple-300:focus { + --gradient-to-color: #d6bcfa; + } + + .sm\:focus\:to-purple-400:focus { + --gradient-to-color: #b794f4; + } + + .sm\:focus\:to-purple-500:focus { + --gradient-to-color: #9f7aea; + } + + .sm\:focus\:to-purple-600:focus { + --gradient-to-color: #805ad5; + } + + .sm\:focus\:to-purple-700:focus { + --gradient-to-color: #6b46c1; + } + + .sm\:focus\:to-purple-800:focus { + --gradient-to-color: #553c9a; + } + + .sm\:focus\:to-purple-900:focus { + --gradient-to-color: #44337a; + } + + .sm\:focus\:to-pink-100:focus { + --gradient-to-color: #fff5f7; + } + + .sm\:focus\:to-pink-200:focus { + --gradient-to-color: #fed7e2; + } + + .sm\:focus\:to-pink-300:focus { + --gradient-to-color: #fbb6ce; + } + + .sm\:focus\:to-pink-400:focus { + --gradient-to-color: #f687b3; + } + + .sm\:focus\:to-pink-500:focus { + --gradient-to-color: #ed64a6; + } + + .sm\:focus\:to-pink-600:focus { + --gradient-to-color: #d53f8c; + } + + .sm\:focus\:to-pink-700:focus { + --gradient-to-color: #b83280; + } + + .sm\:focus\:to-pink-800:focus { + --gradient-to-color: #97266d; + } + + .sm\:focus\:to-pink-900:focus { + --gradient-to-color: #702459; + } + + .sm\:bg-opacity-0 { + --bg-opacity: 0; + } + + .sm\:bg-opacity-25 { + --bg-opacity: 0.25; + } + + .sm\:bg-opacity-50 { + --bg-opacity: 0.5; + } + + .sm\:bg-opacity-75 { + --bg-opacity: 0.75; + } + + .sm\:bg-opacity-100 { + --bg-opacity: 1; + } + + .sm\:hover\:bg-opacity-0:hover { + --bg-opacity: 0; + } + + .sm\:hover\:bg-opacity-25:hover { + --bg-opacity: 0.25; + } + + .sm\:hover\:bg-opacity-50:hover { + --bg-opacity: 0.5; + } + + .sm\:hover\:bg-opacity-75:hover { + --bg-opacity: 0.75; + } + + .sm\:hover\:bg-opacity-100:hover { + --bg-opacity: 1; + } + + .sm\:focus\:bg-opacity-0:focus { + --bg-opacity: 0; + } + + .sm\:focus\:bg-opacity-25:focus { + --bg-opacity: 0.25; + } + + .sm\:focus\:bg-opacity-50:focus { + --bg-opacity: 0.5; + } + + .sm\:focus\:bg-opacity-75:focus { + --bg-opacity: 0.75; + } + + .sm\:focus\:bg-opacity-100:focus { + --bg-opacity: 1; + } + + .sm\:bg-bottom { + background-position: bottom; + } + + .sm\:bg-center { + background-position: center; + } + + .sm\:bg-left { + background-position: left; + } + + .sm\:bg-left-bottom { + background-position: left bottom; + } + + .sm\:bg-left-top { + background-position: left top; + } + + .sm\:bg-right { + background-position: right; + } + + .sm\:bg-right-bottom { + background-position: right bottom; + } + + .sm\:bg-right-top { + background-position: right top; + } + + .sm\:bg-top { + background-position: top; + } + + .sm\:bg-repeat { + background-repeat: repeat; + } + + .sm\:bg-no-repeat { + background-repeat: no-repeat; + } + + .sm\:bg-repeat-x { + background-repeat: repeat-x; + } + + .sm\:bg-repeat-y { + background-repeat: repeat-y; + } + + .sm\:bg-repeat-round { + background-repeat: round; + } + + .sm\:bg-repeat-space { + background-repeat: space; + } + + .sm\:bg-auto { + background-size: auto; + } + + .sm\:bg-cover { + background-size: cover; + } + + .sm\:bg-contain { + background-size: contain; + } + + .sm\:border-collapse { + border-collapse: collapse; + } + + .sm\:border-separate { + border-collapse: separate; + } + + .sm\:border-transparent { + border-color: transparent; + } + + .sm\:border-current { + border-color: currentColor; + } + + .sm\:border-black { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .sm\:border-white { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .sm\:border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .sm\:border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .sm\:border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .sm\:border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .sm\:border-gray-500 { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .sm\:border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .sm\:border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .sm\:border-gray-800 { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .sm\:border-gray-900 { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .sm\:border-red-100 { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .sm\:border-red-200 { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .sm\:border-red-300 { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .sm\:border-red-400 { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .sm\:border-red-500 { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .sm\:border-red-600 { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .sm\:border-red-700 { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .sm\:border-red-800 { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .sm\:border-red-900 { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .sm\:border-orange-100 { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .sm\:border-orange-200 { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .sm\:border-orange-300 { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .sm\:border-orange-400 { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .sm\:border-orange-500 { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .sm\:border-orange-600 { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .sm\:border-orange-700 { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .sm\:border-orange-800 { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .sm\:border-orange-900 { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .sm\:border-yellow-100 { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .sm\:border-yellow-200 { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .sm\:border-yellow-300 { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .sm\:border-yellow-400 { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .sm\:border-yellow-500 { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .sm\:border-yellow-600 { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .sm\:border-yellow-700 { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .sm\:border-yellow-800 { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .sm\:border-yellow-900 { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .sm\:border-green-100 { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .sm\:border-green-200 { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .sm\:border-green-300 { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .sm\:border-green-400 { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .sm\:border-green-500 { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .sm\:border-green-600 { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .sm\:border-green-700 { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .sm\:border-green-800 { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .sm\:border-green-900 { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .sm\:border-teal-100 { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .sm\:border-teal-200 { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .sm\:border-teal-300 { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .sm\:border-teal-400 { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .sm\:border-teal-500 { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .sm\:border-teal-600 { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .sm\:border-teal-700 { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .sm\:border-teal-800 { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .sm\:border-teal-900 { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .sm\:border-blue-100 { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .sm\:border-blue-200 { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .sm\:border-blue-300 { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .sm\:border-blue-400 { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .sm\:border-blue-500 { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .sm\:border-blue-600 { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .sm\:border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .sm\:border-blue-800 { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .sm\:border-blue-900 { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .sm\:border-indigo-100 { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .sm\:border-indigo-200 { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .sm\:border-indigo-300 { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .sm\:border-indigo-400 { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .sm\:border-indigo-500 { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .sm\:border-indigo-600 { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .sm\:border-indigo-700 { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .sm\:border-indigo-800 { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .sm\:border-indigo-900 { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .sm\:border-purple-100 { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .sm\:border-purple-200 { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .sm\:border-purple-300 { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .sm\:border-purple-400 { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .sm\:border-purple-500 { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .sm\:border-purple-600 { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .sm\:border-purple-700 { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .sm\:border-purple-800 { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .sm\:border-purple-900 { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .sm\:border-pink-100 { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .sm\:border-pink-200 { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .sm\:border-pink-300 { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .sm\:border-pink-400 { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .sm\:border-pink-500 { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .sm\:border-pink-600 { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .sm\:border-pink-700 { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .sm\:border-pink-800 { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .sm\:border-pink-900 { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .sm\:hover\:border-transparent:hover { + border-color: transparent; + } + + .sm\:hover\:border-current:hover { + border-color: currentColor; + } + + .sm\:hover\:border-black:hover { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .sm\:hover\:border-white:hover { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .sm\:hover\:border-gray-100:hover { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .sm\:hover\:border-gray-200:hover { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .sm\:hover\:border-gray-300:hover { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .sm\:hover\:border-gray-400:hover { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .sm\:hover\:border-gray-500:hover { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .sm\:hover\:border-gray-600:hover { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .sm\:hover\:border-gray-700:hover { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .sm\:hover\:border-gray-800:hover { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .sm\:hover\:border-gray-900:hover { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .sm\:hover\:border-red-100:hover { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .sm\:hover\:border-red-200:hover { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .sm\:hover\:border-red-300:hover { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .sm\:hover\:border-red-400:hover { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .sm\:hover\:border-red-500:hover { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .sm\:hover\:border-red-600:hover { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .sm\:hover\:border-red-700:hover { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .sm\:hover\:border-red-800:hover { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .sm\:hover\:border-red-900:hover { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .sm\:hover\:border-orange-100:hover { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .sm\:hover\:border-orange-200:hover { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .sm\:hover\:border-orange-300:hover { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .sm\:hover\:border-orange-400:hover { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .sm\:hover\:border-orange-500:hover { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .sm\:hover\:border-orange-600:hover { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .sm\:hover\:border-orange-700:hover { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .sm\:hover\:border-orange-800:hover { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .sm\:hover\:border-orange-900:hover { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-100:hover { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-200:hover { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-300:hover { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-400:hover { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-500:hover { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-600:hover { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-700:hover { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-800:hover { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .sm\:hover\:border-yellow-900:hover { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .sm\:hover\:border-green-100:hover { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .sm\:hover\:border-green-200:hover { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .sm\:hover\:border-green-300:hover { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .sm\:hover\:border-green-400:hover { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .sm\:hover\:border-green-500:hover { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .sm\:hover\:border-green-600:hover { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .sm\:hover\:border-green-700:hover { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .sm\:hover\:border-green-800:hover { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .sm\:hover\:border-green-900:hover { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .sm\:hover\:border-teal-100:hover { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .sm\:hover\:border-teal-200:hover { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .sm\:hover\:border-teal-300:hover { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .sm\:hover\:border-teal-400:hover { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .sm\:hover\:border-teal-500:hover { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .sm\:hover\:border-teal-600:hover { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .sm\:hover\:border-teal-700:hover { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .sm\:hover\:border-teal-800:hover { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .sm\:hover\:border-teal-900:hover { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .sm\:hover\:border-blue-100:hover { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .sm\:hover\:border-blue-200:hover { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .sm\:hover\:border-blue-300:hover { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .sm\:hover\:border-blue-400:hover { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .sm\:hover\:border-blue-500:hover { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .sm\:hover\:border-blue-600:hover { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .sm\:hover\:border-blue-700:hover { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .sm\:hover\:border-blue-800:hover { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .sm\:hover\:border-blue-900:hover { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-100:hover { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-200:hover { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-300:hover { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-400:hover { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-500:hover { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-600:hover { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-700:hover { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-800:hover { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .sm\:hover\:border-indigo-900:hover { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .sm\:hover\:border-purple-100:hover { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .sm\:hover\:border-purple-200:hover { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .sm\:hover\:border-purple-300:hover { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .sm\:hover\:border-purple-400:hover { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .sm\:hover\:border-purple-500:hover { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .sm\:hover\:border-purple-600:hover { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .sm\:hover\:border-purple-700:hover { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .sm\:hover\:border-purple-800:hover { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .sm\:hover\:border-purple-900:hover { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .sm\:hover\:border-pink-100:hover { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .sm\:hover\:border-pink-200:hover { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .sm\:hover\:border-pink-300:hover { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .sm\:hover\:border-pink-400:hover { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .sm\:hover\:border-pink-500:hover { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .sm\:hover\:border-pink-600:hover { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .sm\:hover\:border-pink-700:hover { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .sm\:hover\:border-pink-800:hover { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .sm\:hover\:border-pink-900:hover { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .sm\:focus\:border-transparent:focus { + border-color: transparent; + } + + .sm\:focus\:border-current:focus { + border-color: currentColor; + } + + .sm\:focus\:border-black:focus { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .sm\:focus\:border-white:focus { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .sm\:focus\:border-gray-100:focus { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .sm\:focus\:border-gray-200:focus { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .sm\:focus\:border-gray-300:focus { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .sm\:focus\:border-gray-400:focus { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .sm\:focus\:border-gray-500:focus { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .sm\:focus\:border-gray-600:focus { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .sm\:focus\:border-gray-700:focus { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .sm\:focus\:border-gray-800:focus { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .sm\:focus\:border-gray-900:focus { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .sm\:focus\:border-red-100:focus { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .sm\:focus\:border-red-200:focus { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .sm\:focus\:border-red-300:focus { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .sm\:focus\:border-red-400:focus { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .sm\:focus\:border-red-500:focus { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .sm\:focus\:border-red-600:focus { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .sm\:focus\:border-red-700:focus { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .sm\:focus\:border-red-800:focus { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .sm\:focus\:border-red-900:focus { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .sm\:focus\:border-orange-100:focus { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .sm\:focus\:border-orange-200:focus { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .sm\:focus\:border-orange-300:focus { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .sm\:focus\:border-orange-400:focus { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .sm\:focus\:border-orange-500:focus { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .sm\:focus\:border-orange-600:focus { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .sm\:focus\:border-orange-700:focus { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .sm\:focus\:border-orange-800:focus { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .sm\:focus\:border-orange-900:focus { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-100:focus { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-200:focus { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-300:focus { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-400:focus { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-500:focus { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-600:focus { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-700:focus { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-800:focus { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .sm\:focus\:border-yellow-900:focus { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .sm\:focus\:border-green-100:focus { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .sm\:focus\:border-green-200:focus { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .sm\:focus\:border-green-300:focus { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .sm\:focus\:border-green-400:focus { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .sm\:focus\:border-green-500:focus { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .sm\:focus\:border-green-600:focus { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .sm\:focus\:border-green-700:focus { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .sm\:focus\:border-green-800:focus { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .sm\:focus\:border-green-900:focus { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .sm\:focus\:border-teal-100:focus { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .sm\:focus\:border-teal-200:focus { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .sm\:focus\:border-teal-300:focus { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .sm\:focus\:border-teal-400:focus { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .sm\:focus\:border-teal-500:focus { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .sm\:focus\:border-teal-600:focus { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .sm\:focus\:border-teal-700:focus { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .sm\:focus\:border-teal-800:focus { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .sm\:focus\:border-teal-900:focus { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .sm\:focus\:border-blue-100:focus { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .sm\:focus\:border-blue-200:focus { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .sm\:focus\:border-blue-300:focus { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .sm\:focus\:border-blue-400:focus { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .sm\:focus\:border-blue-500:focus { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .sm\:focus\:border-blue-600:focus { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .sm\:focus\:border-blue-700:focus { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .sm\:focus\:border-blue-800:focus { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .sm\:focus\:border-blue-900:focus { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-100:focus { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-200:focus { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-300:focus { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-400:focus { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-500:focus { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-600:focus { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-700:focus { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-800:focus { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .sm\:focus\:border-indigo-900:focus { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .sm\:focus\:border-purple-100:focus { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .sm\:focus\:border-purple-200:focus { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .sm\:focus\:border-purple-300:focus { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .sm\:focus\:border-purple-400:focus { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .sm\:focus\:border-purple-500:focus { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .sm\:focus\:border-purple-600:focus { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .sm\:focus\:border-purple-700:focus { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .sm\:focus\:border-purple-800:focus { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .sm\:focus\:border-purple-900:focus { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .sm\:focus\:border-pink-100:focus { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .sm\:focus\:border-pink-200:focus { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .sm\:focus\:border-pink-300:focus { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .sm\:focus\:border-pink-400:focus { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .sm\:focus\:border-pink-500:focus { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .sm\:focus\:border-pink-600:focus { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .sm\:focus\:border-pink-700:focus { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .sm\:focus\:border-pink-800:focus { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .sm\:focus\:border-pink-900:focus { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .sm\:border-opacity-0 { + --border-opacity: 0; + } + + .sm\:border-opacity-25 { + --border-opacity: 0.25; + } + + .sm\:border-opacity-50 { + --border-opacity: 0.5; + } + + .sm\:border-opacity-75 { + --border-opacity: 0.75; + } + + .sm\:border-opacity-100 { + --border-opacity: 1; + } + + .sm\:hover\:border-opacity-0:hover { + --border-opacity: 0; + } + + .sm\:hover\:border-opacity-25:hover { + --border-opacity: 0.25; + } + + .sm\:hover\:border-opacity-50:hover { + --border-opacity: 0.5; + } + + .sm\:hover\:border-opacity-75:hover { + --border-opacity: 0.75; + } + + .sm\:hover\:border-opacity-100:hover { + --border-opacity: 1; + } + + .sm\:focus\:border-opacity-0:focus { + --border-opacity: 0; + } + + .sm\:focus\:border-opacity-25:focus { + --border-opacity: 0.25; + } + + .sm\:focus\:border-opacity-50:focus { + --border-opacity: 0.5; + } + + .sm\:focus\:border-opacity-75:focus { + --border-opacity: 0.75; + } + + .sm\:focus\:border-opacity-100:focus { + --border-opacity: 1; + } + + .sm\:rounded-none { + border-radius: 0; + } + + .sm\:rounded-sm { + border-radius: 0.125rem; + } + + .sm\:rounded { + border-radius: 0.25rem; + } + + .sm\:rounded-md { + border-radius: 0.375rem; + } + + .sm\:rounded-lg { + border-radius: 0.5rem; + } + + .sm\:rounded-full { + border-radius: 9999px; + } + + .sm\:rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .sm\:rounded-r-none { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .sm\:rounded-b-none { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + + .sm\:rounded-l-none { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .sm\:rounded-t-sm { + border-top-left-radius: 0.125rem; + border-top-right-radius: 0.125rem; + } + + .sm\:rounded-r-sm { + border-top-right-radius: 0.125rem; + border-bottom-right-radius: 0.125rem; + } + + .sm\:rounded-b-sm { + border-bottom-right-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .sm\:rounded-l-sm { + border-top-left-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .sm\:rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + + .sm\:rounded-r { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + } + + .sm\:rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .sm\:rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .sm\:rounded-t-md { + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; + } + + .sm\:rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + } + + .sm\:rounded-b-md { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .sm\:rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .sm\:rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } + + .sm\:rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + } + + .sm\:rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .sm\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .sm\:rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px; + } + + .sm\:rounded-r-full { + border-top-right-radius: 9999px; + border-bottom-right-radius: 9999px; + } + + .sm\:rounded-b-full { + border-bottom-right-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .sm\:rounded-l-full { + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .sm\:rounded-tl-none { + border-top-left-radius: 0; + } + + .sm\:rounded-tr-none { + border-top-right-radius: 0; + } + + .sm\:rounded-br-none { + border-bottom-right-radius: 0; + } + + .sm\:rounded-bl-none { + border-bottom-left-radius: 0; + } + + .sm\:rounded-tl-sm { + border-top-left-radius: 0.125rem; + } + + .sm\:rounded-tr-sm { + border-top-right-radius: 0.125rem; + } + + .sm\:rounded-br-sm { + border-bottom-right-radius: 0.125rem; + } + + .sm\:rounded-bl-sm { + border-bottom-left-radius: 0.125rem; + } + + .sm\:rounded-tl { + border-top-left-radius: 0.25rem; + } + + .sm\:rounded-tr { + border-top-right-radius: 0.25rem; + } + + .sm\:rounded-br { + border-bottom-right-radius: 0.25rem; + } + + .sm\:rounded-bl { + border-bottom-left-radius: 0.25rem; + } + + .sm\:rounded-tl-md { + border-top-left-radius: 0.375rem; + } + + .sm\:rounded-tr-md { + border-top-right-radius: 0.375rem; + } + + .sm\:rounded-br-md { + border-bottom-right-radius: 0.375rem; + } + + .sm\:rounded-bl-md { + border-bottom-left-radius: 0.375rem; + } + + .sm\:rounded-tl-lg { + border-top-left-radius: 0.5rem; + } + + .sm\:rounded-tr-lg { + border-top-right-radius: 0.5rem; + } + + .sm\:rounded-br-lg { + border-bottom-right-radius: 0.5rem; + } + + .sm\:rounded-bl-lg { + border-bottom-left-radius: 0.5rem; + } + + .sm\:rounded-tl-full { + border-top-left-radius: 9999px; + } + + .sm\:rounded-tr-full { + border-top-right-radius: 9999px; + } + + .sm\:rounded-br-full { + border-bottom-right-radius: 9999px; + } + + .sm\:rounded-bl-full { + border-bottom-left-radius: 9999px; + } + + .sm\:border-solid { + border-style: solid; + } + + .sm\:border-dashed { + border-style: dashed; + } + + .sm\:border-dotted { + border-style: dotted; + } + + .sm\:border-double { + border-style: double; + } + + .sm\:border-none { + border-style: none; + } + + .sm\:border-0 { + border-width: 0; + } + + .sm\:border-2 { + border-width: 2px; + } + + .sm\:border-4 { + border-width: 4px; + } + + .sm\:border-8 { + border-width: 8px; + } + + .sm\:border { + border-width: 1px; + } + + .sm\:border-t-0 { + border-top-width: 0; + } + + .sm\:border-r-0 { + border-right-width: 0; + } + + .sm\:border-b-0 { + border-bottom-width: 0; + } + + .sm\:border-l-0 { + border-left-width: 0; + } + + .sm\:border-t-2 { + border-top-width: 2px; + } + + .sm\:border-r-2 { + border-right-width: 2px; + } + + .sm\:border-b-2 { + border-bottom-width: 2px; + } + + .sm\:border-l-2 { + border-left-width: 2px; + } + + .sm\:border-t-4 { + border-top-width: 4px; + } + + .sm\:border-r-4 { + border-right-width: 4px; + } + + .sm\:border-b-4 { + border-bottom-width: 4px; + } + + .sm\:border-l-4 { + border-left-width: 4px; + } + + .sm\:border-t-8 { + border-top-width: 8px; + } + + .sm\:border-r-8 { + border-right-width: 8px; + } + + .sm\:border-b-8 { + border-bottom-width: 8px; + } + + .sm\:border-l-8 { + border-left-width: 8px; + } + + .sm\:border-t { + border-top-width: 1px; + } + + .sm\:border-r { + border-right-width: 1px; + } + + .sm\:border-b { + border-bottom-width: 1px; + } + + .sm\:border-l { + border-left-width: 1px; + } + + .sm\:box-border { + box-sizing: border-box; + } + + .sm\:box-content { + box-sizing: content-box; + } + + .sm\:cursor-auto { + cursor: auto; + } + + .sm\:cursor-default { + cursor: default; + } + + .sm\:cursor-pointer { + cursor: pointer; + } + + .sm\:cursor-wait { + cursor: wait; + } + + .sm\:cursor-text { + cursor: text; + } + + .sm\:cursor-move { + cursor: move; + } + + .sm\:cursor-not-allowed { + cursor: not-allowed; + } + + .sm\:block { + display: block; + } + + .sm\:inline-block { + display: inline-block; + } + + .sm\:inline { + display: inline; + } + + .sm\:flex { + display: flex; + } + + .sm\:inline-flex { + display: inline-flex; + } + + .sm\:table { + display: table; + } + + .sm\:table-caption { + display: table-caption; + } + + .sm\:table-cell { + display: table-cell; + } + + .sm\:table-column { + display: table-column; + } + + .sm\:table-column-group { + display: table-column-group; + } + + .sm\:table-footer-group { + display: table-footer-group; + } + + .sm\:table-header-group { + display: table-header-group; + } + + .sm\:table-row-group { + display: table-row-group; + } + + .sm\:table-row { + display: table-row; + } + + .sm\:flow-root { + display: flow-root; + } + + .sm\:grid { + display: grid; + } + + .sm\:inline-grid { + display: inline-grid; + } + + .sm\:contents { + display: contents; + } + + .sm\:hidden { + display: none; + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:flex-row-reverse { + flex-direction: row-reverse; + } + + .sm\:flex-col { + flex-direction: column; + } + + .sm\:flex-col-reverse { + flex-direction: column-reverse; + } + + .sm\:flex-wrap { + flex-wrap: wrap; + } + + .sm\:flex-wrap-reverse { + flex-wrap: wrap-reverse; + } + + .sm\:flex-no-wrap { + flex-wrap: nowrap; + } + + .sm\:place-items-auto { + place-items: auto; + } + + .sm\:place-items-start { + place-items: start; + } + + .sm\:place-items-end { + place-items: end; + } + + .sm\:place-items-center { + place-items: center; + } + + .sm\:place-items-stretch { + place-items: stretch; + } + + .sm\:place-content-center { + place-content: center; + } + + .sm\:place-content-start { + place-content: start; + } + + .sm\:place-content-end { + place-content: end; + } + + .sm\:place-content-between { + place-content: space-between; + } + + .sm\:place-content-around { + place-content: space-around; + } + + .sm\:place-content-evenly { + place-content: space-evenly; + } + + .sm\:place-content-stretch { + place-content: stretch; + } + + .sm\:place-self-auto { + place-self: auto; + } + + .sm\:place-self-start { + place-self: start; + } + + .sm\:place-self-end { + place-self: end; + } + + .sm\:place-self-center { + place-self: center; + } + + .sm\:place-self-stretch { + place-self: stretch; + } + + .sm\:items-start { + align-items: flex-start; + } + + .sm\:items-end { + align-items: flex-end; + } + + .sm\:items-center { + align-items: center; + } + + .sm\:items-baseline { + align-items: baseline; + } + + .sm\:items-stretch { + align-items: stretch; + } + + .sm\:content-center { + align-content: center; + } + + .sm\:content-start { + align-content: flex-start; + } + + .sm\:content-end { + align-content: flex-end; + } + + .sm\:content-between { + align-content: space-between; + } + + .sm\:content-around { + align-content: space-around; + } + + .sm\:content-evenly { + align-content: space-evenly; + } + + .sm\:self-auto { + align-self: auto; + } + + .sm\:self-start { + align-self: flex-start; + } + + .sm\:self-end { + align-self: flex-end; + } + + .sm\:self-center { + align-self: center; + } + + .sm\:self-stretch { + align-self: stretch; + } + + .sm\:justify-items-auto { + justify-items: auto; + } + + .sm\:justify-items-start { + justify-items: start; + } + + .sm\:justify-items-end { + justify-items: end; + } + + .sm\:justify-items-center { + justify-items: center; + } + + .sm\:justify-items-stretch { + justify-items: stretch; + } + + .sm\:justify-start { + justify-content: flex-start; + } + + .sm\:justify-end { + justify-content: flex-end; + } + + .sm\:justify-center { + justify-content: center; + } + + .sm\:justify-between { + justify-content: space-between; + } + + .sm\:justify-around { + justify-content: space-around; + } + + .sm\:justify-evenly { + justify-content: space-evenly; + } + + .sm\:justify-self-auto { + justify-self: auto; + } + + .sm\:justify-self-start { + justify-self: start; + } + + .sm\:justify-self-end { + justify-self: end; + } + + .sm\:justify-self-center { + justify-self: center; + } + + .sm\:justify-self-stretch { + justify-self: stretch; + } + + .sm\:flex-1 { + flex: 1 1 0%; + } + + .sm\:flex-auto { + flex: 1 1 auto; + } + + .sm\:flex-initial { + flex: 0 1 auto; + } + + .sm\:flex-none { + flex: none; + } + + .sm\:flex-grow-0 { + flex-grow: 0; + } + + .sm\:flex-grow { + flex-grow: 1; + } + + .sm\:flex-shrink-0 { + flex-shrink: 0; + } + + .sm\:flex-shrink { + flex-shrink: 1; + } + + .sm\:order-1 { + order: 1; + } + + .sm\:order-2 { + order: 2; + } + + .sm\:order-3 { + order: 3; + } + + .sm\:order-4 { + order: 4; + } + + .sm\:order-5 { + order: 5; + } + + .sm\:order-6 { + order: 6; + } + + .sm\:order-7 { + order: 7; + } + + .sm\:order-8 { + order: 8; + } + + .sm\:order-9 { + order: 9; + } + + .sm\:order-10 { + order: 10; + } + + .sm\:order-11 { + order: 11; + } + + .sm\:order-12 { + order: 12; + } + + .sm\:order-first { + order: -9999; + } + + .sm\:order-last { + order: 9999; + } + + .sm\:order-none { + order: 0; + } + + .sm\:float-right { + float: right; + } + + .sm\:float-left { + float: left; + } + + .sm\:float-none { + float: none; + } + + .sm\:clearfix:after { + content: ""; + display: table; + clear: both; + } + + .sm\:clear-left { + clear: left; + } + + .sm\:clear-right { + clear: right; + } + + .sm\:clear-both { + clear: both; + } + + .sm\:clear-none { + clear: none; + } + + .sm\:font-sans { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + } + + .sm\:font-serif { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + } + + .sm\:font-mono { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .sm\:font-hairline { + font-weight: 100; + } + + .sm\:font-thin { + font-weight: 200; + } + + .sm\:font-light { + font-weight: 300; + } + + .sm\:font-normal { + font-weight: 400; + } + + .sm\:font-medium { + font-weight: 500; + } + + .sm\:font-semibold { + font-weight: 600; + } + + .sm\:font-bold { + font-weight: 700; + } + + .sm\:font-extrabold { + font-weight: 800; + } + + .sm\:font-black { + font-weight: 900; + } + + .sm\:hover\:font-hairline:hover { + font-weight: 100; + } + + .sm\:hover\:font-thin:hover { + font-weight: 200; + } + + .sm\:hover\:font-light:hover { + font-weight: 300; + } + + .sm\:hover\:font-normal:hover { + font-weight: 400; + } + + .sm\:hover\:font-medium:hover { + font-weight: 500; + } + + .sm\:hover\:font-semibold:hover { + font-weight: 600; + } + + .sm\:hover\:font-bold:hover { + font-weight: 700; + } + + .sm\:hover\:font-extrabold:hover { + font-weight: 800; + } + + .sm\:hover\:font-black:hover { + font-weight: 900; + } + + .sm\:focus\:font-hairline:focus { + font-weight: 100; + } + + .sm\:focus\:font-thin:focus { + font-weight: 200; + } + + .sm\:focus\:font-light:focus { + font-weight: 300; + } + + .sm\:focus\:font-normal:focus { + font-weight: 400; + } + + .sm\:focus\:font-medium:focus { + font-weight: 500; + } + + .sm\:focus\:font-semibold:focus { + font-weight: 600; + } + + .sm\:focus\:font-bold:focus { + font-weight: 700; + } + + .sm\:focus\:font-extrabold:focus { + font-weight: 800; + } + + .sm\:focus\:font-black:focus { + font-weight: 900; + } + + .sm\:h-0 { + height: 0; + } + + .sm\:h-1 { + height: 0.25rem; + } + + .sm\:h-2 { + height: 0.5rem; + } + + .sm\:h-3 { + height: 0.75rem; + } + + .sm\:h-4 { + height: 1rem; + } + + .sm\:h-5 { + height: 1.25rem; + } + + .sm\:h-6 { + height: 1.5rem; + } + + .sm\:h-8 { + height: 2rem; + } + + .sm\:h-10 { + height: 2.5rem; + } + + .sm\:h-12 { + height: 3rem; + } + + .sm\:h-16 { + height: 4rem; + } + + .sm\:h-20 { + height: 5rem; + } + + .sm\:h-24 { + height: 6rem; + } + + .sm\:h-32 { + height: 8rem; + } + + .sm\:h-40 { + height: 10rem; + } + + .sm\:h-48 { + height: 12rem; + } + + .sm\:h-56 { + height: 14rem; + } + + .sm\:h-64 { + height: 16rem; + } + + .sm\:h-auto { + height: auto; + } + + .sm\:h-px { + height: 1px; + } + + .sm\:h-full { + height: 100%; + } + + .sm\:h-screen { + height: 100vh; + } + + .sm\:text-xs { + font-size: 0.75rem; + } + + .sm\:text-sm { + font-size: 0.875rem; + } + + .sm\:text-base { + font-size: 1rem; + } + + .sm\:text-lg { + font-size: 1.125rem; + } + + .sm\:text-xl { + font-size: 1.25rem; + } + + .sm\:text-2xl { + font-size: 1.5rem; + } + + .sm\:text-3xl { + font-size: 1.875rem; + } + + .sm\:text-4xl { + font-size: 2.25rem; + } + + .sm\:text-5xl { + font-size: 3rem; + } + + .sm\:text-6xl { + font-size: 4rem; + } + + .sm\:leading-3 { + line-height: .75rem; + } + + .sm\:leading-4 { + line-height: 1rem; + } + + .sm\:leading-5 { + line-height: 1.25rem; + } + + .sm\:leading-6 { + line-height: 1.5rem; + } + + .sm\:leading-7 { + line-height: 1.75rem; + } + + .sm\:leading-8 { + line-height: 2rem; + } + + .sm\:leading-9 { + line-height: 2.25rem; + } + + .sm\:leading-10 { + line-height: 2.5rem; + } + + .sm\:leading-none { + line-height: 1; + } + + .sm\:leading-tight { + line-height: 1.25; + } + + .sm\:leading-snug { + line-height: 1.375; + } + + .sm\:leading-normal { + line-height: 1.5; + } + + .sm\:leading-relaxed { + line-height: 1.625; + } + + .sm\:leading-loose { + line-height: 2; + } + + .sm\:list-inside { + list-style-position: inside; + } + + .sm\:list-outside { + list-style-position: outside; + } + + .sm\:list-none { + list-style-type: none; + } + + .sm\:list-disc { + list-style-type: disc; + } + + .sm\:list-decimal { + list-style-type: decimal; + } + + .sm\:m-0 { + margin: 0; + } + + .sm\:m-1 { + margin: 0.25rem; + } + + .sm\:m-2 { + margin: 0.5rem; + } + + .sm\:m-3 { + margin: 0.75rem; + } + + .sm\:m-4 { + margin: 1rem; + } + + .sm\:m-5 { + margin: 1.25rem; + } + + .sm\:m-6 { + margin: 1.5rem; + } + + .sm\:m-8 { + margin: 2rem; + } + + .sm\:m-10 { + margin: 2.5rem; + } + + .sm\:m-12 { + margin: 3rem; + } + + .sm\:m-16 { + margin: 4rem; + } + + .sm\:m-20 { + margin: 5rem; + } + + .sm\:m-24 { + margin: 6rem; + } + + .sm\:m-32 { + margin: 8rem; + } + + .sm\:m-40 { + margin: 10rem; + } + + .sm\:m-48 { + margin: 12rem; + } + + .sm\:m-56 { + margin: 14rem; + } + + .sm\:m-64 { + margin: 16rem; + } + + .sm\:m-auto { + margin: auto; + } + + .sm\:m-px { + margin: 1px; + } + + .sm\:-m-1 { + margin: -0.25rem; + } + + .sm\:-m-2 { + margin: -0.5rem; + } + + .sm\:-m-3 { + margin: -0.75rem; + } + + .sm\:-m-4 { + margin: -1rem; + } + + .sm\:-m-5 { + margin: -1.25rem; + } + + .sm\:-m-6 { + margin: -1.5rem; + } + + .sm\:-m-8 { + margin: -2rem; + } + + .sm\:-m-10 { + margin: -2.5rem; + } + + .sm\:-m-12 { + margin: -3rem; + } + + .sm\:-m-16 { + margin: -4rem; + } + + .sm\:-m-20 { + margin: -5rem; + } + + .sm\:-m-24 { + margin: -6rem; + } + + .sm\:-m-32 { + margin: -8rem; + } + + .sm\:-m-40 { + margin: -10rem; + } + + .sm\:-m-48 { + margin: -12rem; + } + + .sm\:-m-56 { + margin: -14rem; + } + + .sm\:-m-64 { + margin: -16rem; + } + + .sm\:-m-px { + margin: -1px; + } + + .sm\:my-0 { + margin-top: 0; + margin-bottom: 0; + } + + .sm\:mx-0 { + margin-left: 0; + margin-right: 0; + } + + .sm\:my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + } + + .sm\:mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + + .sm\:my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + .sm\:mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + + .sm\:my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + } + + .sm\:mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; + } + + .sm\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + .sm\:mx-4 { + margin-left: 1rem; + margin-right: 1rem; + } + + .sm\:my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + } + + .sm\:mx-5 { + margin-left: 1.25rem; + margin-right: 1.25rem; + } + + .sm\:my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + + .sm\:mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; + } + + .sm\:my-8 { + margin-top: 2rem; + margin-bottom: 2rem; + } + + .sm\:mx-8 { + margin-left: 2rem; + margin-right: 2rem; + } + + .sm\:my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; + } + + .sm\:mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; + } + + .sm\:my-12 { + margin-top: 3rem; + margin-bottom: 3rem; + } + + .sm\:mx-12 { + margin-left: 3rem; + margin-right: 3rem; + } + + .sm\:my-16 { + margin-top: 4rem; + margin-bottom: 4rem; + } + + .sm\:mx-16 { + margin-left: 4rem; + margin-right: 4rem; + } + + .sm\:my-20 { + margin-top: 5rem; + margin-bottom: 5rem; + } + + .sm\:mx-20 { + margin-left: 5rem; + margin-right: 5rem; + } + + .sm\:my-24 { + margin-top: 6rem; + margin-bottom: 6rem; + } + + .sm\:mx-24 { + margin-left: 6rem; + margin-right: 6rem; + } + + .sm\:my-32 { + margin-top: 8rem; + margin-bottom: 8rem; + } + + .sm\:mx-32 { + margin-left: 8rem; + margin-right: 8rem; + } + + .sm\:my-40 { + margin-top: 10rem; + margin-bottom: 10rem; + } + + .sm\:mx-40 { + margin-left: 10rem; + margin-right: 10rem; + } + + .sm\:my-48 { + margin-top: 12rem; + margin-bottom: 12rem; + } + + .sm\:mx-48 { + margin-left: 12rem; + margin-right: 12rem; + } + + .sm\:my-56 { + margin-top: 14rem; + margin-bottom: 14rem; + } + + .sm\:mx-56 { + margin-left: 14rem; + margin-right: 14rem; + } + + .sm\:my-64 { + margin-top: 16rem; + margin-bottom: 16rem; + } + + .sm\:mx-64 { + margin-left: 16rem; + margin-right: 16rem; + } + + .sm\:my-auto { + margin-top: auto; + margin-bottom: auto; + } + + .sm\:mx-auto { + margin-left: auto; + margin-right: auto; + } + + .sm\:my-px { + margin-top: 1px; + margin-bottom: 1px; + } + + .sm\:mx-px { + margin-left: 1px; + margin-right: 1px; + } + + .sm\:-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; + } + + .sm\:-mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; + } + + .sm\:-my-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; + } + + .sm\:-mx-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; + } + + .sm\:-my-3 { + margin-top: -0.75rem; + margin-bottom: -0.75rem; + } + + .sm\:-mx-3 { + margin-left: -0.75rem; + margin-right: -0.75rem; + } + + .sm\:-my-4 { + margin-top: -1rem; + margin-bottom: -1rem; + } + + .sm\:-mx-4 { + margin-left: -1rem; + margin-right: -1rem; + } + + .sm\:-my-5 { + margin-top: -1.25rem; + margin-bottom: -1.25rem; + } + + .sm\:-mx-5 { + margin-left: -1.25rem; + margin-right: -1.25rem; + } + + .sm\:-my-6 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; + } + + .sm\:-mx-6 { + margin-left: -1.5rem; + margin-right: -1.5rem; + } + + .sm\:-my-8 { + margin-top: -2rem; + margin-bottom: -2rem; + } + + .sm\:-mx-8 { + margin-left: -2rem; + margin-right: -2rem; + } + + .sm\:-my-10 { + margin-top: -2.5rem; + margin-bottom: -2.5rem; + } + + .sm\:-mx-10 { + margin-left: -2.5rem; + margin-right: -2.5rem; + } + + .sm\:-my-12 { + margin-top: -3rem; + margin-bottom: -3rem; + } + + .sm\:-mx-12 { + margin-left: -3rem; + margin-right: -3rem; + } + + .sm\:-my-16 { + margin-top: -4rem; + margin-bottom: -4rem; + } + + .sm\:-mx-16 { + margin-left: -4rem; + margin-right: -4rem; + } + + .sm\:-my-20 { + margin-top: -5rem; + margin-bottom: -5rem; + } + + .sm\:-mx-20 { + margin-left: -5rem; + margin-right: -5rem; + } + + .sm\:-my-24 { + margin-top: -6rem; + margin-bottom: -6rem; + } + + .sm\:-mx-24 { + margin-left: -6rem; + margin-right: -6rem; + } + + .sm\:-my-32 { + margin-top: -8rem; + margin-bottom: -8rem; + } + + .sm\:-mx-32 { + margin-left: -8rem; + margin-right: -8rem; + } + + .sm\:-my-40 { + margin-top: -10rem; + margin-bottom: -10rem; + } + + .sm\:-mx-40 { + margin-left: -10rem; + margin-right: -10rem; + } + + .sm\:-my-48 { + margin-top: -12rem; + margin-bottom: -12rem; + } + + .sm\:-mx-48 { + margin-left: -12rem; + margin-right: -12rem; + } + + .sm\:-my-56 { + margin-top: -14rem; + margin-bottom: -14rem; + } + + .sm\:-mx-56 { + margin-left: -14rem; + margin-right: -14rem; + } + + .sm\:-my-64 { + margin-top: -16rem; + margin-bottom: -16rem; + } + + .sm\:-mx-64 { + margin-left: -16rem; + margin-right: -16rem; + } + + .sm\:-my-px { + margin-top: -1px; + margin-bottom: -1px; + } + + .sm\:-mx-px { + margin-left: -1px; + margin-right: -1px; + } + + .sm\:mt-0 { + margin-top: 0; + } + + .sm\:mr-0 { + margin-right: 0; + } + + .sm\:mb-0 { + margin-bottom: 0; + } + + .sm\:ml-0 { + margin-left: 0; + } + + .sm\:mt-1 { + margin-top: 0.25rem; + } + + .sm\:mr-1 { + margin-right: 0.25rem; + } + + .sm\:mb-1 { + margin-bottom: 0.25rem; + } + + .sm\:ml-1 { + margin-left: 0.25rem; + } + + .sm\:mt-2 { + margin-top: 0.5rem; + } + + .sm\:mr-2 { + margin-right: 0.5rem; + } + + .sm\:mb-2 { + margin-bottom: 0.5rem; + } + + .sm\:ml-2 { + margin-left: 0.5rem; + } + + .sm\:mt-3 { + margin-top: 0.75rem; + } + + .sm\:mr-3 { + margin-right: 0.75rem; + } + + .sm\:mb-3 { + margin-bottom: 0.75rem; + } + + .sm\:ml-3 { + margin-left: 0.75rem; + } + + .sm\:mt-4 { + margin-top: 1rem; + } + + .sm\:mr-4 { + margin-right: 1rem; + } + + .sm\:mb-4 { + margin-bottom: 1rem; + } + + .sm\:ml-4 { + margin-left: 1rem; + } + + .sm\:mt-5 { + margin-top: 1.25rem; + } + + .sm\:mr-5 { + margin-right: 1.25rem; + } + + .sm\:mb-5 { + margin-bottom: 1.25rem; + } + + .sm\:ml-5 { + margin-left: 1.25rem; + } + + .sm\:mt-6 { + margin-top: 1.5rem; + } + + .sm\:mr-6 { + margin-right: 1.5rem; + } + + .sm\:mb-6 { + margin-bottom: 1.5rem; + } + + .sm\:ml-6 { + margin-left: 1.5rem; + } + + .sm\:mt-8 { + margin-top: 2rem; + } + + .sm\:mr-8 { + margin-right: 2rem; + } + + .sm\:mb-8 { + margin-bottom: 2rem; + } + + .sm\:ml-8 { + margin-left: 2rem; + } + + .sm\:mt-10 { + margin-top: 2.5rem; + } + + .sm\:mr-10 { + margin-right: 2.5rem; + } + + .sm\:mb-10 { + margin-bottom: 2.5rem; + } + + .sm\:ml-10 { + margin-left: 2.5rem; + } + + .sm\:mt-12 { + margin-top: 3rem; + } + + .sm\:mr-12 { + margin-right: 3rem; + } + + .sm\:mb-12 { + margin-bottom: 3rem; + } + + .sm\:ml-12 { + margin-left: 3rem; + } + + .sm\:mt-16 { + margin-top: 4rem; + } + + .sm\:mr-16 { + margin-right: 4rem; + } + + .sm\:mb-16 { + margin-bottom: 4rem; + } + + .sm\:ml-16 { + margin-left: 4rem; + } + + .sm\:mt-20 { + margin-top: 5rem; + } + + .sm\:mr-20 { + margin-right: 5rem; + } + + .sm\:mb-20 { + margin-bottom: 5rem; + } + + .sm\:ml-20 { + margin-left: 5rem; + } + + .sm\:mt-24 { + margin-top: 6rem; + } + + .sm\:mr-24 { + margin-right: 6rem; + } + + .sm\:mb-24 { + margin-bottom: 6rem; + } + + .sm\:ml-24 { + margin-left: 6rem; + } + + .sm\:mt-32 { + margin-top: 8rem; + } + + .sm\:mr-32 { + margin-right: 8rem; + } + + .sm\:mb-32 { + margin-bottom: 8rem; + } + + .sm\:ml-32 { + margin-left: 8rem; + } + + .sm\:mt-40 { + margin-top: 10rem; + } + + .sm\:mr-40 { + margin-right: 10rem; + } + + .sm\:mb-40 { + margin-bottom: 10rem; + } + + .sm\:ml-40 { + margin-left: 10rem; + } + + .sm\:mt-48 { + margin-top: 12rem; + } + + .sm\:mr-48 { + margin-right: 12rem; + } + + .sm\:mb-48 { + margin-bottom: 12rem; + } + + .sm\:ml-48 { + margin-left: 12rem; + } + + .sm\:mt-56 { + margin-top: 14rem; + } + + .sm\:mr-56 { + margin-right: 14rem; + } + + .sm\:mb-56 { + margin-bottom: 14rem; + } + + .sm\:ml-56 { + margin-left: 14rem; + } + + .sm\:mt-64 { + margin-top: 16rem; + } + + .sm\:mr-64 { + margin-right: 16rem; + } + + .sm\:mb-64 { + margin-bottom: 16rem; + } + + .sm\:ml-64 { + margin-left: 16rem; + } + + .sm\:mt-auto { + margin-top: auto; + } + + .sm\:mr-auto { + margin-right: auto; + } + + .sm\:mb-auto { + margin-bottom: auto; + } + + .sm\:ml-auto { + margin-left: auto; + } + + .sm\:mt-px { + margin-top: 1px; + } + + .sm\:mr-px { + margin-right: 1px; + } + + .sm\:mb-px { + margin-bottom: 1px; + } + + .sm\:ml-px { + margin-left: 1px; + } + + .sm\:-mt-1 { + margin-top: -0.25rem; + } + + .sm\:-mr-1 { + margin-right: -0.25rem; + } + + .sm\:-mb-1 { + margin-bottom: -0.25rem; + } + + .sm\:-ml-1 { + margin-left: -0.25rem; + } + + .sm\:-mt-2 { + margin-top: -0.5rem; + } + + .sm\:-mr-2 { + margin-right: -0.5rem; + } + + .sm\:-mb-2 { + margin-bottom: -0.5rem; + } + + .sm\:-ml-2 { + margin-left: -0.5rem; + } + + .sm\:-mt-3 { + margin-top: -0.75rem; + } + + .sm\:-mr-3 { + margin-right: -0.75rem; + } + + .sm\:-mb-3 { + margin-bottom: -0.75rem; + } + + .sm\:-ml-3 { + margin-left: -0.75rem; + } + + .sm\:-mt-4 { + margin-top: -1rem; + } + + .sm\:-mr-4 { + margin-right: -1rem; + } + + .sm\:-mb-4 { + margin-bottom: -1rem; + } + + .sm\:-ml-4 { + margin-left: -1rem; + } + + .sm\:-mt-5 { + margin-top: -1.25rem; + } + + .sm\:-mr-5 { + margin-right: -1.25rem; + } + + .sm\:-mb-5 { + margin-bottom: -1.25rem; + } + + .sm\:-ml-5 { + margin-left: -1.25rem; + } + + .sm\:-mt-6 { + margin-top: -1.5rem; + } + + .sm\:-mr-6 { + margin-right: -1.5rem; + } + + .sm\:-mb-6 { + margin-bottom: -1.5rem; + } + + .sm\:-ml-6 { + margin-left: -1.5rem; + } + + .sm\:-mt-8 { + margin-top: -2rem; + } + + .sm\:-mr-8 { + margin-right: -2rem; + } + + .sm\:-mb-8 { + margin-bottom: -2rem; + } + + .sm\:-ml-8 { + margin-left: -2rem; + } + + .sm\:-mt-10 { + margin-top: -2.5rem; + } + + .sm\:-mr-10 { + margin-right: -2.5rem; + } + + .sm\:-mb-10 { + margin-bottom: -2.5rem; + } + + .sm\:-ml-10 { + margin-left: -2.5rem; + } + + .sm\:-mt-12 { + margin-top: -3rem; + } + + .sm\:-mr-12 { + margin-right: -3rem; + } + + .sm\:-mb-12 { + margin-bottom: -3rem; + } + + .sm\:-ml-12 { + margin-left: -3rem; + } + + .sm\:-mt-16 { + margin-top: -4rem; + } + + .sm\:-mr-16 { + margin-right: -4rem; + } + + .sm\:-mb-16 { + margin-bottom: -4rem; + } + + .sm\:-ml-16 { + margin-left: -4rem; + } + + .sm\:-mt-20 { + margin-top: -5rem; + } + + .sm\:-mr-20 { + margin-right: -5rem; + } + + .sm\:-mb-20 { + margin-bottom: -5rem; + } + + .sm\:-ml-20 { + margin-left: -5rem; + } + + .sm\:-mt-24 { + margin-top: -6rem; + } + + .sm\:-mr-24 { + margin-right: -6rem; + } + + .sm\:-mb-24 { + margin-bottom: -6rem; + } + + .sm\:-ml-24 { + margin-left: -6rem; + } + + .sm\:-mt-32 { + margin-top: -8rem; + } + + .sm\:-mr-32 { + margin-right: -8rem; + } + + .sm\:-mb-32 { + margin-bottom: -8rem; + } + + .sm\:-ml-32 { + margin-left: -8rem; + } + + .sm\:-mt-40 { + margin-top: -10rem; + } + + .sm\:-mr-40 { + margin-right: -10rem; + } + + .sm\:-mb-40 { + margin-bottom: -10rem; + } + + .sm\:-ml-40 { + margin-left: -10rem; + } + + .sm\:-mt-48 { + margin-top: -12rem; + } + + .sm\:-mr-48 { + margin-right: -12rem; + } + + .sm\:-mb-48 { + margin-bottom: -12rem; + } + + .sm\:-ml-48 { + margin-left: -12rem; + } + + .sm\:-mt-56 { + margin-top: -14rem; + } + + .sm\:-mr-56 { + margin-right: -14rem; + } + + .sm\:-mb-56 { + margin-bottom: -14rem; + } + + .sm\:-ml-56 { + margin-left: -14rem; + } + + .sm\:-mt-64 { + margin-top: -16rem; + } + + .sm\:-mr-64 { + margin-right: -16rem; + } + + .sm\:-mb-64 { + margin-bottom: -16rem; + } + + .sm\:-ml-64 { + margin-left: -16rem; + } + + .sm\:-mt-px { + margin-top: -1px; + } + + .sm\:-mr-px { + margin-right: -1px; + } + + .sm\:-mb-px { + margin-bottom: -1px; + } + + .sm\:-ml-px { + margin-left: -1px; + } + + .sm\:max-h-full { + max-height: 100%; + } + + .sm\:max-h-screen { + max-height: 100vh; + } + + .sm\:max-w-none { + max-width: none; + } + + .sm\:max-w-xs { + max-width: 20rem; + } + + .sm\:max-w-sm { + max-width: 24rem; + } + + .sm\:max-w-md { + max-width: 28rem; + } + + .sm\:max-w-lg { + max-width: 32rem; + } + + .sm\:max-w-xl { + max-width: 36rem; + } + + .sm\:max-w-2xl { + max-width: 42rem; + } + + .sm\:max-w-3xl { + max-width: 48rem; + } + + .sm\:max-w-4xl { + max-width: 56rem; + } + + .sm\:max-w-5xl { + max-width: 64rem; + } + + .sm\:max-w-6xl { + max-width: 72rem; + } + + .sm\:max-w-full { + max-width: 100%; + } + + .sm\:max-w-screen-sm { + max-width: 640px; + } + + .sm\:max-w-screen-md { + max-width: 768px; + } + + .sm\:max-w-screen-lg { + max-width: 1024px; + } + + .sm\:max-w-screen-xl { + max-width: 1280px; + } + + .sm\:min-h-0 { + min-height: 0; + } + + .sm\:min-h-full { + min-height: 100%; + } + + .sm\:min-h-screen { + min-height: 100vh; + } + + .sm\:min-w-0 { + min-width: 0; + } + + .sm\:min-w-full { + min-width: 100%; + } + + .sm\:object-contain { + -o-object-fit: contain; + object-fit: contain; + } + + .sm\:object-cover { + -o-object-fit: cover; + object-fit: cover; + } + + .sm\:object-fill { + -o-object-fit: fill; + object-fit: fill; + } + + .sm\:object-none { + -o-object-fit: none; + object-fit: none; + } + + .sm\:object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; + } + + .sm\:object-bottom { + -o-object-position: bottom; + object-position: bottom; + } + + .sm\:object-center { + -o-object-position: center; + object-position: center; + } + + .sm\:object-left { + -o-object-position: left; + object-position: left; + } + + .sm\:object-left-bottom { + -o-object-position: left bottom; + object-position: left bottom; + } + + .sm\:object-left-top { + -o-object-position: left top; + object-position: left top; + } + + .sm\:object-right { + -o-object-position: right; + object-position: right; + } + + .sm\:object-right-bottom { + -o-object-position: right bottom; + object-position: right bottom; + } + + .sm\:object-right-top { + -o-object-position: right top; + object-position: right top; + } + + .sm\:object-top { + -o-object-position: top; + object-position: top; + } + + .sm\:opacity-0 { + opacity: 0; + } + + .sm\:opacity-25 { + opacity: 0.25; + } + + .sm\:opacity-50 { + opacity: 0.5; + } + + .sm\:opacity-75 { + opacity: 0.75; + } + + .sm\:opacity-100 { + opacity: 1; + } + + .sm\:hover\:opacity-0:hover { + opacity: 0; + } + + .sm\:hover\:opacity-25:hover { + opacity: 0.25; + } + + .sm\:hover\:opacity-50:hover { + opacity: 0.5; + } + + .sm\:hover\:opacity-75:hover { + opacity: 0.75; + } + + .sm\:hover\:opacity-100:hover { + opacity: 1; + } + + .sm\:focus\:opacity-0:focus { + opacity: 0; + } + + .sm\:focus\:opacity-25:focus { + opacity: 0.25; + } + + .sm\:focus\:opacity-50:focus { + opacity: 0.5; + } + + .sm\:focus\:opacity-75:focus { + opacity: 0.75; + } + + .sm\:focus\:opacity-100:focus { + opacity: 1; + } + + .sm\:outline-none { + outline: 0; + } + + .sm\:focus\:outline-none:focus { + outline: 0; + } + + .sm\:overflow-auto { + overflow: auto; + } + + .sm\:overflow-hidden { + overflow: hidden; + } + + .sm\:overflow-visible { + overflow: visible; + } + + .sm\:overflow-scroll { + overflow: scroll; + } + + .sm\:overflow-x-auto { + overflow-x: auto; + } + + .sm\:overflow-y-auto { + overflow-y: auto; + } + + .sm\:overflow-x-hidden { + overflow-x: hidden; + } + + .sm\:overflow-y-hidden { + overflow-y: hidden; + } + + .sm\:overflow-x-visible { + overflow-x: visible; + } + + .sm\:overflow-y-visible { + overflow-y: visible; + } + + .sm\:overflow-x-scroll { + overflow-x: scroll; + } + + .sm\:overflow-y-scroll { + overflow-y: scroll; + } + + .sm\:scrolling-touch { + -webkit-overflow-scrolling: touch; + } + + .sm\:scrolling-auto { + -webkit-overflow-scrolling: auto; + } + + .sm\:overscroll-auto { + -ms-scroll-chaining: chained; + overscroll-behavior: auto; + } + + .sm\:overscroll-contain { + -ms-scroll-chaining: none; + overscroll-behavior: contain; + } + + .sm\:overscroll-none { + -ms-scroll-chaining: none; + overscroll-behavior: none; + } + + .sm\:overscroll-y-auto { + overscroll-behavior-y: auto; + } + + .sm\:overscroll-y-contain { + overscroll-behavior-y: contain; + } + + .sm\:overscroll-y-none { + overscroll-behavior-y: none; + } + + .sm\:overscroll-x-auto { + overscroll-behavior-x: auto; + } + + .sm\:overscroll-x-contain { + overscroll-behavior-x: contain; + } + + .sm\:overscroll-x-none { + overscroll-behavior-x: none; + } + + .sm\:p-0 { + padding: 0; + } + + .sm\:p-1 { + padding: 0.25rem; + } + + .sm\:p-2 { + padding: 0.5rem; + } + + .sm\:p-3 { + padding: 0.75rem; + } + + .sm\:p-4 { + padding: 1rem; + } + + .sm\:p-5 { + padding: 1.25rem; + } + + .sm\:p-6 { + padding: 1.5rem; + } + + .sm\:p-8 { + padding: 2rem; + } + + .sm\:p-10 { + padding: 2.5rem; + } + + .sm\:p-12 { + padding: 3rem; + } + + .sm\:p-16 { + padding: 4rem; + } + + .sm\:p-20 { + padding: 5rem; + } + + .sm\:p-24 { + padding: 6rem; + } + + .sm\:p-32 { + padding: 8rem; + } + + .sm\:p-40 { + padding: 10rem; + } + + .sm\:p-48 { + padding: 12rem; + } + + .sm\:p-56 { + padding: 14rem; + } + + .sm\:p-64 { + padding: 16rem; + } + + .sm\:p-px { + padding: 1px; + } + + .sm\:py-0 { + padding-top: 0; + padding-bottom: 0; + } + + .sm\:px-0 { + padding-left: 0; + padding-right: 0; + } + + .sm\:py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } + + .sm\:px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + .sm\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + .sm\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .sm\:py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + + .sm\:px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + + .sm\:py-4 { + padding-top: 1rem; + padding-bottom: 1rem; + } + + .sm\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .sm\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + + .sm\:px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; + } + + .sm\:py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .sm\:py-8 { + padding-top: 2rem; + padding-bottom: 2rem; + } + + .sm\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .sm\:py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; + } + + .sm\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .sm\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + + .sm\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .sm\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .sm\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .sm\:py-20 { + padding-top: 5rem; + padding-bottom: 5rem; + } + + .sm\:px-20 { + padding-left: 5rem; + padding-right: 5rem; + } + + .sm\:py-24 { + padding-top: 6rem; + padding-bottom: 6rem; + } + + .sm\:px-24 { + padding-left: 6rem; + padding-right: 6rem; + } + + .sm\:py-32 { + padding-top: 8rem; + padding-bottom: 8rem; + } + + .sm\:px-32 { + padding-left: 8rem; + padding-right: 8rem; + } + + .sm\:py-40 { + padding-top: 10rem; + padding-bottom: 10rem; + } + + .sm\:px-40 { + padding-left: 10rem; + padding-right: 10rem; + } + + .sm\:py-48 { + padding-top: 12rem; + padding-bottom: 12rem; + } + + .sm\:px-48 { + padding-left: 12rem; + padding-right: 12rem; + } + + .sm\:py-56 { + padding-top: 14rem; + padding-bottom: 14rem; + } + + .sm\:px-56 { + padding-left: 14rem; + padding-right: 14rem; + } + + .sm\:py-64 { + padding-top: 16rem; + padding-bottom: 16rem; + } + + .sm\:px-64 { + padding-left: 16rem; + padding-right: 16rem; + } + + .sm\:py-px { + padding-top: 1px; + padding-bottom: 1px; + } + + .sm\:px-px { + padding-left: 1px; + padding-right: 1px; + } + + .sm\:pt-0 { + padding-top: 0; + } + + .sm\:pr-0 { + padding-right: 0; + } + + .sm\:pb-0 { + padding-bottom: 0; + } + + .sm\:pl-0 { + padding-left: 0; + } + + .sm\:pt-1 { + padding-top: 0.25rem; + } + + .sm\:pr-1 { + padding-right: 0.25rem; + } + + .sm\:pb-1 { + padding-bottom: 0.25rem; + } + + .sm\:pl-1 { + padding-left: 0.25rem; + } + + .sm\:pt-2 { + padding-top: 0.5rem; + } + + .sm\:pr-2 { + padding-right: 0.5rem; + } + + .sm\:pb-2 { + padding-bottom: 0.5rem; + } + + .sm\:pl-2 { + padding-left: 0.5rem; + } + + .sm\:pt-3 { + padding-top: 0.75rem; + } + + .sm\:pr-3 { + padding-right: 0.75rem; + } + + .sm\:pb-3 { + padding-bottom: 0.75rem; + } + + .sm\:pl-3 { + padding-left: 0.75rem; + } + + .sm\:pt-4 { + padding-top: 1rem; + } + + .sm\:pr-4 { + padding-right: 1rem; + } + + .sm\:pb-4 { + padding-bottom: 1rem; + } + + .sm\:pl-4 { + padding-left: 1rem; + } + + .sm\:pt-5 { + padding-top: 1.25rem; + } + + .sm\:pr-5 { + padding-right: 1.25rem; + } + + .sm\:pb-5 { + padding-bottom: 1.25rem; + } + + .sm\:pl-5 { + padding-left: 1.25rem; + } + + .sm\:pt-6 { + padding-top: 1.5rem; + } + + .sm\:pr-6 { + padding-right: 1.5rem; + } + + .sm\:pb-6 { + padding-bottom: 1.5rem; + } + + .sm\:pl-6 { + padding-left: 1.5rem; + } + + .sm\:pt-8 { + padding-top: 2rem; + } + + .sm\:pr-8 { + padding-right: 2rem; + } + + .sm\:pb-8 { + padding-bottom: 2rem; + } + + .sm\:pl-8 { + padding-left: 2rem; + } + + .sm\:pt-10 { + padding-top: 2.5rem; + } + + .sm\:pr-10 { + padding-right: 2.5rem; + } + + .sm\:pb-10 { + padding-bottom: 2.5rem; + } + + .sm\:pl-10 { + padding-left: 2.5rem; + } + + .sm\:pt-12 { + padding-top: 3rem; + } + + .sm\:pr-12 { + padding-right: 3rem; + } + + .sm\:pb-12 { + padding-bottom: 3rem; + } + + .sm\:pl-12 { + padding-left: 3rem; + } + + .sm\:pt-16 { + padding-top: 4rem; + } + + .sm\:pr-16 { + padding-right: 4rem; + } + + .sm\:pb-16 { + padding-bottom: 4rem; + } + + .sm\:pl-16 { + padding-left: 4rem; + } + + .sm\:pt-20 { + padding-top: 5rem; + } + + .sm\:pr-20 { + padding-right: 5rem; + } + + .sm\:pb-20 { + padding-bottom: 5rem; + } + + .sm\:pl-20 { + padding-left: 5rem; + } + + .sm\:pt-24 { + padding-top: 6rem; + } + + .sm\:pr-24 { + padding-right: 6rem; + } + + .sm\:pb-24 { + padding-bottom: 6rem; + } + + .sm\:pl-24 { + padding-left: 6rem; + } + + .sm\:pt-32 { + padding-top: 8rem; + } + + .sm\:pr-32 { + padding-right: 8rem; + } + + .sm\:pb-32 { + padding-bottom: 8rem; + } + + .sm\:pl-32 { + padding-left: 8rem; + } + + .sm\:pt-40 { + padding-top: 10rem; + } + + .sm\:pr-40 { + padding-right: 10rem; + } + + .sm\:pb-40 { + padding-bottom: 10rem; + } + + .sm\:pl-40 { + padding-left: 10rem; + } + + .sm\:pt-48 { + padding-top: 12rem; + } + + .sm\:pr-48 { + padding-right: 12rem; + } + + .sm\:pb-48 { + padding-bottom: 12rem; + } + + .sm\:pl-48 { + padding-left: 12rem; + } + + .sm\:pt-56 { + padding-top: 14rem; + } + + .sm\:pr-56 { + padding-right: 14rem; + } + + .sm\:pb-56 { + padding-bottom: 14rem; + } + + .sm\:pl-56 { + padding-left: 14rem; + } + + .sm\:pt-64 { + padding-top: 16rem; + } + + .sm\:pr-64 { + padding-right: 16rem; + } + + .sm\:pb-64 { + padding-bottom: 16rem; + } + + .sm\:pl-64 { + padding-left: 16rem; + } + + .sm\:pt-px { + padding-top: 1px; + } + + .sm\:pr-px { + padding-right: 1px; + } + + .sm\:pb-px { + padding-bottom: 1px; + } + + .sm\:pl-px { + padding-left: 1px; + } + + .sm\:placeholder-transparent::-moz-placeholder { + color: transparent; + } + + .sm\:placeholder-transparent:-ms-input-placeholder { + color: transparent; + } + + .sm\:placeholder-transparent::placeholder { + color: transparent; + } + + .sm\:placeholder-current::-moz-placeholder { + color: currentColor; + } + + .sm\:placeholder-current:-ms-input-placeholder { + color: currentColor; + } + + .sm\:placeholder-current::placeholder { + color: currentColor; + } + + .sm\:placeholder-black::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:placeholder-black:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:placeholder-black::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:placeholder-white::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-white:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-white::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-100::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-200::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-300::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-500::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-600::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-700::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-800::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-gray-900::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-200::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-300::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-400::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-500::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-600::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-700::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-800::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:placeholder-red-900::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-100::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-200::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-300::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-400::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-500::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-600::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-700::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-800::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:placeholder-orange-900::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-100::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-200::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-300::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-400::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-500::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-600::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-700::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-800::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:placeholder-yellow-900::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-100::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-200::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-300::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-400::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-500::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-600::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-700::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-800::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:placeholder-green-900::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-100::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-200::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-300::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-400::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-500::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-600::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-700::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-800::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:placeholder-teal-900::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-100::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-200::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-300::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-400::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-500::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-600::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-700::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-800::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-blue-900::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-100::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-200::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-300::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-400::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-500::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-600::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-700::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-800::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:placeholder-indigo-900::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-100::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-200::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-300::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-400::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-500::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-600::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-700::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-800::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:placeholder-purple-900::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-200::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-300::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-400::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-500::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-600::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-700::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-800::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:placeholder-pink-900::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-transparent:focus::-moz-placeholder { + color: transparent; + } + + .sm\:focus\:placeholder-transparent:focus:-ms-input-placeholder { + color: transparent; + } + + .sm\:focus\:placeholder-transparent:focus::placeholder { + color: transparent; + } + + .sm\:focus\:placeholder-current:focus::-moz-placeholder { + color: currentColor; + } + + .sm\:focus\:placeholder-current:focus:-ms-input-placeholder { + color: currentColor; + } + + .sm\:focus\:placeholder-current:focus::placeholder { + color: currentColor; + } + + .sm\:focus\:placeholder-black:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-black:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-black:focus::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-white:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-white:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-white:focus::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-200:focus::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-300:focus::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-400:focus::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-500:focus::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-600:focus::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-gray-900:focus::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-300:focus::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-400:focus::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-500:focus::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-600:focus::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-red-900:focus::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-200:focus::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-600:focus::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-orange-900:focus::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-300:focus::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-800:focus::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-yellow-900:focus::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-300:focus::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-400:focus::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-500:focus::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-600:focus::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-800:focus::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-green-900:focus::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-100:focus::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-200:focus::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-300:focus::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-400:focus::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-500:focus::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-600:focus::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-800:focus::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-teal-900:focus::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-200:focus::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-300:focus::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-400:focus::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-500:focus::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-600:focus::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-blue-900:focus::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-300:focus::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-400:focus::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-500:focus::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-600:focus::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-800:focus::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-indigo-900:focus::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-100:focus::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-200:focus::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-300:focus::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-400:focus::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-500:focus::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-600:focus::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-700:focus::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-800:focus::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-purple-900:focus::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-800:focus::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:focus\:placeholder-pink-900:focus::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .sm\:placeholder-opacity-0::-moz-placeholder { + --placeholder-opacity: 0; + } + + .sm\:placeholder-opacity-0:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .sm\:placeholder-opacity-0::placeholder { + --placeholder-opacity: 0; + } + + .sm\:placeholder-opacity-25::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:placeholder-opacity-25:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:placeholder-opacity-25::placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:placeholder-opacity-50::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:placeholder-opacity-50:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:placeholder-opacity-50::placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:placeholder-opacity-75::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:placeholder-opacity-75:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:placeholder-opacity-75::placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:placeholder-opacity-100::-moz-placeholder { + --placeholder-opacity: 1; + } + + .sm\:placeholder-opacity-100:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .sm\:placeholder-opacity-100::placeholder { + --placeholder-opacity: 1; + } + + .sm\:focus\:placeholder-opacity-0:focus::-moz-placeholder { + --placeholder-opacity: 0; + } + + .sm\:focus\:placeholder-opacity-0:focus:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .sm\:focus\:placeholder-opacity-0:focus::placeholder { + --placeholder-opacity: 0; + } + + .sm\:focus\:placeholder-opacity-25:focus::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:focus\:placeholder-opacity-25:focus:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:focus\:placeholder-opacity-25:focus::placeholder { + --placeholder-opacity: 0.25; + } + + .sm\:focus\:placeholder-opacity-50:focus::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:focus\:placeholder-opacity-50:focus:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:focus\:placeholder-opacity-50:focus::placeholder { + --placeholder-opacity: 0.5; + } + + .sm\:focus\:placeholder-opacity-75:focus::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:focus\:placeholder-opacity-75:focus:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:focus\:placeholder-opacity-75:focus::placeholder { + --placeholder-opacity: 0.75; + } + + .sm\:focus\:placeholder-opacity-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + } + + .sm\:focus\:placeholder-opacity-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .sm\:focus\:placeholder-opacity-100:focus::placeholder { + --placeholder-opacity: 1; + } + + .sm\:pointer-events-none { + pointer-events: none; + } + + .sm\:pointer-events-auto { + pointer-events: auto; + } + + .sm\:static { + position: static; + } + + .sm\:fixed { + position: fixed; + } + + .sm\:absolute { + position: absolute; + } + + .sm\:relative { + position: relative; + } + + .sm\:sticky { + position: -webkit-sticky; + position: sticky; + } + + .sm\:inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .sm\:inset-auto { + top: auto; + right: auto; + bottom: auto; + left: auto; + } + + .sm\:inset-y-0 { + top: 0; + bottom: 0; + } + + .sm\:inset-x-0 { + right: 0; + left: 0; + } + + .sm\:inset-y-auto { + top: auto; + bottom: auto; + } + + .sm\:inset-x-auto { + right: auto; + left: auto; + } + + .sm\:top-0 { + top: 0; + } + + .sm\:right-0 { + right: 0; + } + + .sm\:bottom-0 { + bottom: 0; + } + + .sm\:left-0 { + left: 0; + } + + .sm\:top-auto { + top: auto; + } + + .sm\:right-auto { + right: auto; + } + + .sm\:bottom-auto { + bottom: auto; + } + + .sm\:left-auto { + left: auto; + } + + .sm\:resize-none { + resize: none; + } + + .sm\:resize-y { + resize: vertical; + } + + .sm\:resize-x { + resize: horizontal; + } + + .sm\:resize { + resize: both; + } + + .sm\:shadow-xs { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .sm\:shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .sm\:shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .sm\:shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .sm\:shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .sm\:shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .sm\:shadow-inner { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:shadow-outline { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .sm\:shadow-none { + box-shadow: none; + } + + .sm\:hover\:shadow-xs:hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .sm\:hover\:shadow-sm:hover { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .sm\:hover\:shadow:hover { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .sm\:hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .sm\:hover\:shadow-xl:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .sm\:hover\:shadow-2xl:hover { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .sm\:hover\:shadow-inner:hover { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:hover\:shadow-outline:hover { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .sm\:hover\:shadow-none:hover { + box-shadow: none; + } + + .sm\:focus\:shadow-xs:focus { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .sm\:focus\:shadow-sm:focus { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .sm\:focus\:shadow:focus { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:focus\:shadow-md:focus { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .sm\:focus\:shadow-lg:focus { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .sm\:focus\:shadow-xl:focus { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .sm\:focus\:shadow-2xl:focus { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .sm\:focus\:shadow-inner:focus { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .sm\:focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .sm\:focus\:shadow-none:focus { + box-shadow: none; + } + + .sm\:fill-current { + fill: currentColor; + } + + .sm\:stroke-current { + stroke: currentColor; + } + + .sm\:stroke-0 { + stroke-width: 0; + } + + .sm\:stroke-1 { + stroke-width: 1; + } + + .sm\:stroke-2 { + stroke-width: 2; + } + + .sm\:table-auto { + table-layout: auto; + } + + .sm\:table-fixed { + table-layout: fixed; + } + + .sm\:text-left { + text-align: left; + } + + .sm\:text-center { + text-align: center; + } + + .sm\:text-right { + text-align: right; + } + + .sm\:text-justify { + text-align: justify; + } + + .sm\:text-transparent { + color: transparent; + } + + .sm\:text-current { + color: currentColor; + } + + .sm\:text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .sm\:text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .sm\:text-gray-100 { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .sm\:text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .sm\:text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .sm\:text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .sm\:text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .sm\:text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .sm\:text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .sm\:text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .sm\:text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .sm\:text-red-100 { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .sm\:text-red-200 { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .sm\:text-red-300 { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .sm\:text-red-400 { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .sm\:text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .sm\:text-red-600 { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .sm\:text-red-700 { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .sm\:text-red-800 { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .sm\:text-red-900 { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .sm\:text-orange-100 { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .sm\:text-orange-200 { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .sm\:text-orange-300 { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .sm\:text-orange-400 { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .sm\:text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .sm\:text-orange-600 { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .sm\:text-orange-700 { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .sm\:text-orange-800 { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .sm\:text-orange-900 { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .sm\:text-yellow-100 { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .sm\:text-yellow-200 { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .sm\:text-yellow-300 { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .sm\:text-yellow-400 { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .sm\:text-yellow-500 { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .sm\:text-yellow-600 { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .sm\:text-yellow-700 { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .sm\:text-yellow-800 { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .sm\:text-yellow-900 { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .sm\:text-green-100 { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .sm\:text-green-200 { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .sm\:text-green-300 { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .sm\:text-green-400 { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .sm\:text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .sm\:text-green-600 { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .sm\:text-green-700 { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .sm\:text-green-800 { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .sm\:text-green-900 { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .sm\:text-teal-100 { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .sm\:text-teal-200 { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .sm\:text-teal-300 { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .sm\:text-teal-400 { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .sm\:text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .sm\:text-teal-600 { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .sm\:text-teal-700 { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .sm\:text-teal-800 { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .sm\:text-teal-900 { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .sm\:text-blue-100 { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .sm\:text-blue-200 { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .sm\:text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .sm\:text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .sm\:text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .sm\:text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .sm\:text-blue-700 { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .sm\:text-blue-800 { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .sm\:text-blue-900 { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .sm\:text-indigo-100 { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .sm\:text-indigo-200 { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .sm\:text-indigo-300 { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .sm\:text-indigo-400 { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .sm\:text-indigo-500 { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .sm\:text-indigo-600 { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .sm\:text-indigo-700 { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .sm\:text-indigo-800 { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .sm\:text-indigo-900 { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .sm\:text-purple-100 { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .sm\:text-purple-200 { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .sm\:text-purple-300 { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .sm\:text-purple-400 { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .sm\:text-purple-500 { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .sm\:text-purple-600 { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .sm\:text-purple-700 { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .sm\:text-purple-800 { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .sm\:text-purple-900 { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .sm\:text-pink-100 { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .sm\:text-pink-200 { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .sm\:text-pink-300 { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .sm\:text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .sm\:text-pink-500 { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .sm\:text-pink-600 { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .sm\:text-pink-700 { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .sm\:text-pink-800 { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .sm\:text-pink-900 { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .sm\:hover\:text-transparent:hover { + color: transparent; + } + + .sm\:hover\:text-current:hover { + color: currentColor; + } + + .sm\:hover\:text-black:hover { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .sm\:hover\:text-white:hover { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .sm\:hover\:text-gray-100:hover { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .sm\:hover\:text-gray-200:hover { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .sm\:hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .sm\:hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .sm\:hover\:text-gray-500:hover { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .sm\:hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .sm\:hover\:text-gray-700:hover { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .sm\:hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .sm\:hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .sm\:hover\:text-red-100:hover { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .sm\:hover\:text-red-200:hover { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .sm\:hover\:text-red-300:hover { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .sm\:hover\:text-red-400:hover { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .sm\:hover\:text-red-500:hover { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .sm\:hover\:text-red-600:hover { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .sm\:hover\:text-red-700:hover { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .sm\:hover\:text-red-800:hover { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .sm\:hover\:text-red-900:hover { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .sm\:hover\:text-orange-100:hover { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .sm\:hover\:text-orange-200:hover { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .sm\:hover\:text-orange-300:hover { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .sm\:hover\:text-orange-400:hover { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .sm\:hover\:text-orange-500:hover { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .sm\:hover\:text-orange-600:hover { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .sm\:hover\:text-orange-700:hover { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .sm\:hover\:text-orange-800:hover { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .sm\:hover\:text-orange-900:hover { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-100:hover { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-200:hover { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-300:hover { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-400:hover { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-500:hover { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-600:hover { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-700:hover { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-800:hover { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .sm\:hover\:text-yellow-900:hover { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .sm\:hover\:text-green-100:hover { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .sm\:hover\:text-green-200:hover { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .sm\:hover\:text-green-300:hover { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .sm\:hover\:text-green-400:hover { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .sm\:hover\:text-green-500:hover { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .sm\:hover\:text-green-600:hover { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .sm\:hover\:text-green-700:hover { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .sm\:hover\:text-green-800:hover { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .sm\:hover\:text-green-900:hover { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .sm\:hover\:text-teal-100:hover { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .sm\:hover\:text-teal-200:hover { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .sm\:hover\:text-teal-300:hover { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .sm\:hover\:text-teal-400:hover { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .sm\:hover\:text-teal-500:hover { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .sm\:hover\:text-teal-600:hover { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .sm\:hover\:text-teal-700:hover { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .sm\:hover\:text-teal-800:hover { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .sm\:hover\:text-teal-900:hover { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .sm\:hover\:text-blue-100:hover { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .sm\:hover\:text-blue-200:hover { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .sm\:hover\:text-blue-300:hover { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .sm\:hover\:text-blue-400:hover { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .sm\:hover\:text-blue-500:hover { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .sm\:hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .sm\:hover\:text-blue-700:hover { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .sm\:hover\:text-blue-800:hover { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .sm\:hover\:text-blue-900:hover { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-100:hover { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-200:hover { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-300:hover { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-400:hover { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-500:hover { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-600:hover { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-700:hover { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-800:hover { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .sm\:hover\:text-indigo-900:hover { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .sm\:hover\:text-purple-100:hover { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .sm\:hover\:text-purple-200:hover { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .sm\:hover\:text-purple-300:hover { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .sm\:hover\:text-purple-400:hover { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .sm\:hover\:text-purple-500:hover { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .sm\:hover\:text-purple-600:hover { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .sm\:hover\:text-purple-700:hover { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .sm\:hover\:text-purple-800:hover { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .sm\:hover\:text-purple-900:hover { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .sm\:hover\:text-pink-100:hover { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .sm\:hover\:text-pink-200:hover { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .sm\:hover\:text-pink-300:hover { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .sm\:hover\:text-pink-400:hover { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .sm\:hover\:text-pink-500:hover { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .sm\:hover\:text-pink-600:hover { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .sm\:hover\:text-pink-700:hover { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .sm\:hover\:text-pink-800:hover { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .sm\:hover\:text-pink-900:hover { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .sm\:focus\:text-transparent:focus { + color: transparent; + } + + .sm\:focus\:text-current:focus { + color: currentColor; + } + + .sm\:focus\:text-black:focus { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .sm\:focus\:text-white:focus { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .sm\:focus\:text-gray-100:focus { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .sm\:focus\:text-gray-200:focus { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .sm\:focus\:text-gray-300:focus { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .sm\:focus\:text-gray-400:focus { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .sm\:focus\:text-gray-500:focus { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .sm\:focus\:text-gray-600:focus { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .sm\:focus\:text-gray-700:focus { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .sm\:focus\:text-gray-800:focus { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .sm\:focus\:text-gray-900:focus { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .sm\:focus\:text-red-100:focus { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .sm\:focus\:text-red-200:focus { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .sm\:focus\:text-red-300:focus { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .sm\:focus\:text-red-400:focus { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .sm\:focus\:text-red-500:focus { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .sm\:focus\:text-red-600:focus { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .sm\:focus\:text-red-700:focus { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .sm\:focus\:text-red-800:focus { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .sm\:focus\:text-red-900:focus { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .sm\:focus\:text-orange-100:focus { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .sm\:focus\:text-orange-200:focus { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .sm\:focus\:text-orange-300:focus { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .sm\:focus\:text-orange-400:focus { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .sm\:focus\:text-orange-500:focus { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .sm\:focus\:text-orange-600:focus { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .sm\:focus\:text-orange-700:focus { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .sm\:focus\:text-orange-800:focus { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .sm\:focus\:text-orange-900:focus { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-100:focus { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-200:focus { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-300:focus { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-400:focus { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-500:focus { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-600:focus { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-700:focus { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-800:focus { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .sm\:focus\:text-yellow-900:focus { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .sm\:focus\:text-green-100:focus { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .sm\:focus\:text-green-200:focus { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .sm\:focus\:text-green-300:focus { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .sm\:focus\:text-green-400:focus { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .sm\:focus\:text-green-500:focus { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .sm\:focus\:text-green-600:focus { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .sm\:focus\:text-green-700:focus { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .sm\:focus\:text-green-800:focus { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .sm\:focus\:text-green-900:focus { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .sm\:focus\:text-teal-100:focus { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .sm\:focus\:text-teal-200:focus { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .sm\:focus\:text-teal-300:focus { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .sm\:focus\:text-teal-400:focus { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .sm\:focus\:text-teal-500:focus { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .sm\:focus\:text-teal-600:focus { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .sm\:focus\:text-teal-700:focus { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .sm\:focus\:text-teal-800:focus { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .sm\:focus\:text-teal-900:focus { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .sm\:focus\:text-blue-100:focus { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .sm\:focus\:text-blue-200:focus { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .sm\:focus\:text-blue-300:focus { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .sm\:focus\:text-blue-400:focus { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .sm\:focus\:text-blue-500:focus { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .sm\:focus\:text-blue-600:focus { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .sm\:focus\:text-blue-700:focus { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .sm\:focus\:text-blue-800:focus { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .sm\:focus\:text-blue-900:focus { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-100:focus { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-200:focus { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-300:focus { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-400:focus { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-500:focus { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-600:focus { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-700:focus { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-800:focus { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .sm\:focus\:text-indigo-900:focus { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .sm\:focus\:text-purple-100:focus { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .sm\:focus\:text-purple-200:focus { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .sm\:focus\:text-purple-300:focus { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .sm\:focus\:text-purple-400:focus { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .sm\:focus\:text-purple-500:focus { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .sm\:focus\:text-purple-600:focus { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .sm\:focus\:text-purple-700:focus { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .sm\:focus\:text-purple-800:focus { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .sm\:focus\:text-purple-900:focus { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .sm\:focus\:text-pink-100:focus { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .sm\:focus\:text-pink-200:focus { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .sm\:focus\:text-pink-300:focus { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .sm\:focus\:text-pink-400:focus { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .sm\:focus\:text-pink-500:focus { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .sm\:focus\:text-pink-600:focus { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .sm\:focus\:text-pink-700:focus { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .sm\:focus\:text-pink-800:focus { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .sm\:focus\:text-pink-900:focus { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .sm\:text-opacity-0 { + --text-opacity: 0; + } + + .sm\:text-opacity-25 { + --text-opacity: 0.25; + } + + .sm\:text-opacity-50 { + --text-opacity: 0.5; + } + + .sm\:text-opacity-75 { + --text-opacity: 0.75; + } + + .sm\:text-opacity-100 { + --text-opacity: 1; + } + + .sm\:hover\:text-opacity-0:hover { + --text-opacity: 0; + } + + .sm\:hover\:text-opacity-25:hover { + --text-opacity: 0.25; + } + + .sm\:hover\:text-opacity-50:hover { + --text-opacity: 0.5; + } + + .sm\:hover\:text-opacity-75:hover { + --text-opacity: 0.75; + } + + .sm\:hover\:text-opacity-100:hover { + --text-opacity: 1; + } + + .sm\:focus\:text-opacity-0:focus { + --text-opacity: 0; + } + + .sm\:focus\:text-opacity-25:focus { + --text-opacity: 0.25; + } + + .sm\:focus\:text-opacity-50:focus { + --text-opacity: 0.5; + } + + .sm\:focus\:text-opacity-75:focus { + --text-opacity: 0.75; + } + + .sm\:focus\:text-opacity-100:focus { + --text-opacity: 1; + } + + .sm\:italic { + font-style: italic; + } + + .sm\:not-italic { + font-style: normal; + } + + .sm\:uppercase { + text-transform: uppercase; + } + + .sm\:lowercase { + text-transform: lowercase; + } + + .sm\:capitalize { + text-transform: capitalize; + } + + .sm\:normal-case { + text-transform: none; + } + + .sm\:underline { + text-decoration: underline; + } + + .sm\:line-through { + text-decoration: line-through; + } + + .sm\:no-underline { + text-decoration: none; + } + + .sm\:hover\:underline:hover { + text-decoration: underline; + } + + .sm\:hover\:line-through:hover { + text-decoration: line-through; + } + + .sm\:hover\:no-underline:hover { + text-decoration: none; + } + + .sm\:focus\:underline:focus { + text-decoration: underline; + } + + .sm\:focus\:line-through:focus { + text-decoration: line-through; + } + + .sm\:focus\:no-underline:focus { + text-decoration: none; + } + + .sm\:antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .sm\:subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + } + + .sm\:ordinal, .sm\:slashed-zero, .sm\:lining-nums, .sm\:oldstyle-nums, .sm\:proportional-nums, .sm\:tabular-nums, .sm\:diagonal-fractions, .sm\:stacked-fractions { + --font-variant-numeric-ordinal: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-slashed-zero: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-figure: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-spacing: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-fraction: var(--tailwind-empty,/*!*/ /*!*/); + font-variant-numeric: var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction); + } + + .sm\:normal-nums { + font-variant-numeric: normal; + } + + .sm\:ordinal { + --font-variant-numeric-ordinal: ordinal; + } + + .sm\:slashed-zero { + --font-variant-numeric-slashed-zero: slashed-zero; + } + + .sm\:lining-nums { + --font-variant-numeric-figure: lining-nums; + } + + .sm\:oldstyle-nums { + --font-variant-numeric-figure: oldstyle-nums; + } + + .sm\:proportional-nums { + --font-variant-numeric-spacing: proportional-nums; + } + + .sm\:tabular-nums { + --font-variant-numeric-spacing: tabular-nums; + } + + .sm\:diagonal-fractions { + --font-variant-numeric-fraction: diagonal-fractions; + } + + .sm\:stacked-fractions { + --font-variant-numeric-fraction: stacked-fractions; + } + + .sm\:tracking-tighter { + letter-spacing: -0.05em; + } + + .sm\:tracking-tight { + letter-spacing: -0.025em; + } + + .sm\:tracking-normal { + letter-spacing: 0; + } + + .sm\:tracking-wide { + letter-spacing: 0.025em; + } + + .sm\:tracking-wider { + letter-spacing: 0.05em; + } + + .sm\:tracking-widest { + letter-spacing: 0.1em; + } + + .sm\:select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .sm\:select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .sm\:select-all { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + } + + .sm\:select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; + } + + .sm\:align-baseline { + vertical-align: baseline; + } + + .sm\:align-top { + vertical-align: top; + } + + .sm\:align-middle { + vertical-align: middle; + } + + .sm\:align-bottom { + vertical-align: bottom; + } + + .sm\:align-text-top { + vertical-align: text-top; + } + + .sm\:align-text-bottom { + vertical-align: text-bottom; + } + + .sm\:visible { + visibility: visible; + } + + .sm\:invisible { + visibility: hidden; + } + + .sm\:whitespace-normal { + white-space: normal; + } + + .sm\:whitespace-no-wrap { + white-space: nowrap; + } + + .sm\:whitespace-pre { + white-space: pre; + } + + .sm\:whitespace-pre-line { + white-space: pre-line; + } + + .sm\:whitespace-pre-wrap { + white-space: pre-wrap; + } + + .sm\:break-normal { + overflow-wrap: normal; + word-break: normal; + } + + .sm\:break-words { + overflow-wrap: break-word; + } + + .sm\:break-all { + word-break: break-all; + } + + .sm\:truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .sm\:w-0 { + width: 0; + } + + .sm\:w-1 { + width: 0.25rem; + } + + .sm\:w-2 { + width: 0.5rem; + } + + .sm\:w-3 { + width: 0.75rem; + } + + .sm\:w-4 { + width: 1rem; + } + + .sm\:w-5 { + width: 1.25rem; + } + + .sm\:w-6 { + width: 1.5rem; + } + + .sm\:w-8 { + width: 2rem; + } + + .sm\:w-10 { + width: 2.5rem; + } + + .sm\:w-12 { + width: 3rem; + } + + .sm\:w-16 { + width: 4rem; + } + + .sm\:w-20 { + width: 5rem; + } + + .sm\:w-24 { + width: 6rem; + } + + .sm\:w-32 { + width: 8rem; + } + + .sm\:w-40 { + width: 10rem; + } + + .sm\:w-48 { + width: 12rem; + } + + .sm\:w-56 { + width: 14rem; + } + + .sm\:w-64 { + width: 16rem; + } + + .sm\:w-auto { + width: auto; + } + + .sm\:w-px { + width: 1px; + } + + .sm\:w-1\/2 { + width: 50%; + } + + .sm\:w-1\/3 { + width: 33.333333%; + } + + .sm\:w-2\/3 { + width: 66.666667%; + } + + .sm\:w-1\/4 { + width: 25%; + } + + .sm\:w-2\/4 { + width: 50%; + } + + .sm\:w-3\/4 { + width: 75%; + } + + .sm\:w-1\/5 { + width: 20%; + } + + .sm\:w-2\/5 { + width: 40%; + } + + .sm\:w-3\/5 { + width: 60%; + } + + .sm\:w-4\/5 { + width: 80%; + } + + .sm\:w-1\/6 { + width: 16.666667%; + } + + .sm\:w-2\/6 { + width: 33.333333%; + } + + .sm\:w-3\/6 { + width: 50%; + } + + .sm\:w-4\/6 { + width: 66.666667%; + } + + .sm\:w-5\/6 { + width: 83.333333%; + } + + .sm\:w-1\/12 { + width: 8.333333%; + } + + .sm\:w-2\/12 { + width: 16.666667%; + } + + .sm\:w-3\/12 { + width: 25%; + } + + .sm\:w-4\/12 { + width: 33.333333%; + } + + .sm\:w-5\/12 { + width: 41.666667%; + } + + .sm\:w-6\/12 { + width: 50%; + } + + .sm\:w-7\/12 { + width: 58.333333%; + } + + .sm\:w-8\/12 { + width: 66.666667%; + } + + .sm\:w-9\/12 { + width: 75%; + } + + .sm\:w-10\/12 { + width: 83.333333%; + } + + .sm\:w-11\/12 { + width: 91.666667%; + } + + .sm\:w-full { + width: 100%; + } + + .sm\:w-screen { + width: 100vw; + } + + .sm\:z-0 { + z-index: 0; + } + + .sm\:z-10 { + z-index: 10; + } + + .sm\:z-20 { + z-index: 20; + } + + .sm\:z-30 { + z-index: 30; + } + + .sm\:z-40 { + z-index: 40; + } + + .sm\:z-50 { + z-index: 50; + } + + .sm\:z-auto { + z-index: auto; + } + + .sm\:gap-0 { + grid-gap: 0; + gap: 0; + } + + .sm\:gap-1 { + grid-gap: 0.25rem; + gap: 0.25rem; + } + + .sm\:gap-2 { + grid-gap: 0.5rem; + gap: 0.5rem; + } + + .sm\:gap-3 { + grid-gap: 0.75rem; + gap: 0.75rem; + } + + .sm\:gap-4 { + grid-gap: 1rem; + gap: 1rem; + } + + .sm\:gap-5 { + grid-gap: 1.25rem; + gap: 1.25rem; + } + + .sm\:gap-6 { + grid-gap: 1.5rem; + gap: 1.5rem; + } + + .sm\:gap-8 { + grid-gap: 2rem; + gap: 2rem; + } + + .sm\:gap-10 { + grid-gap: 2.5rem; + gap: 2.5rem; + } + + .sm\:gap-12 { + grid-gap: 3rem; + gap: 3rem; + } + + .sm\:gap-16 { + grid-gap: 4rem; + gap: 4rem; + } + + .sm\:gap-20 { + grid-gap: 5rem; + gap: 5rem; + } + + .sm\:gap-24 { + grid-gap: 6rem; + gap: 6rem; + } + + .sm\:gap-32 { + grid-gap: 8rem; + gap: 8rem; + } + + .sm\:gap-40 { + grid-gap: 10rem; + gap: 10rem; + } + + .sm\:gap-48 { + grid-gap: 12rem; + gap: 12rem; + } + + .sm\:gap-56 { + grid-gap: 14rem; + gap: 14rem; + } + + .sm\:gap-64 { + grid-gap: 16rem; + gap: 16rem; + } + + .sm\:gap-px { + grid-gap: 1px; + gap: 1px; + } + + .sm\:col-gap-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .sm\:col-gap-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .sm\:col-gap-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .sm\:col-gap-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .sm\:col-gap-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .sm\:col-gap-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .sm\:col-gap-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .sm\:col-gap-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .sm\:col-gap-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .sm\:col-gap-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .sm\:col-gap-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .sm\:col-gap-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .sm\:col-gap-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .sm\:col-gap-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .sm\:col-gap-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .sm\:col-gap-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .sm\:col-gap-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .sm\:col-gap-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .sm\:col-gap-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .sm\:gap-x-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .sm\:gap-x-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .sm\:gap-x-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .sm\:gap-x-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .sm\:gap-x-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .sm\:gap-x-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .sm\:gap-x-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .sm\:gap-x-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .sm\:gap-x-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .sm\:gap-x-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .sm\:gap-x-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .sm\:gap-x-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .sm\:gap-x-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .sm\:gap-x-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .sm\:gap-x-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .sm\:gap-x-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .sm\:gap-x-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .sm\:gap-x-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .sm\:gap-x-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .sm\:row-gap-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .sm\:row-gap-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .sm\:row-gap-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .sm\:row-gap-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .sm\:row-gap-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .sm\:row-gap-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .sm\:row-gap-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .sm\:row-gap-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .sm\:row-gap-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .sm\:row-gap-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .sm\:row-gap-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .sm\:row-gap-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .sm\:row-gap-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .sm\:row-gap-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .sm\:row-gap-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .sm\:row-gap-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .sm\:row-gap-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .sm\:row-gap-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .sm\:row-gap-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .sm\:gap-y-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .sm\:gap-y-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .sm\:gap-y-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .sm\:gap-y-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .sm\:gap-y-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .sm\:gap-y-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .sm\:gap-y-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .sm\:gap-y-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .sm\:gap-y-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .sm\:gap-y-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .sm\:gap-y-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .sm\:gap-y-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .sm\:gap-y-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .sm\:gap-y-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .sm\:gap-y-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .sm\:gap-y-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .sm\:gap-y-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .sm\:gap-y-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .sm\:gap-y-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .sm\:grid-flow-row { + grid-auto-flow: row; + } + + .sm\:grid-flow-col { + grid-auto-flow: column; + } + + .sm\:grid-flow-row-dense { + grid-auto-flow: row dense; + } + + .sm\:grid-flow-col-dense { + grid-auto-flow: column dense; + } + + .sm\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .sm\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .sm\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .sm\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .sm\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .sm\:grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + .sm\:grid-cols-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + + .sm\:grid-cols-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + + .sm\:grid-cols-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + + .sm\:grid-cols-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + + .sm\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + + .sm\:grid-cols-none { + grid-template-columns: none; + } + + .sm\:col-auto { + grid-column: auto; + } + + .sm\:col-span-1 { + grid-column: span 1 / span 1; + } + + .sm\:col-span-2 { + grid-column: span 2 / span 2; + } + + .sm\:col-span-3 { + grid-column: span 3 / span 3; + } + + .sm\:col-span-4 { + grid-column: span 4 / span 4; + } + + .sm\:col-span-5 { + grid-column: span 5 / span 5; + } + + .sm\:col-span-6 { + grid-column: span 6 / span 6; + } + + .sm\:col-span-7 { + grid-column: span 7 / span 7; + } + + .sm\:col-span-8 { + grid-column: span 8 / span 8; + } + + .sm\:col-span-9 { + grid-column: span 9 / span 9; + } + + .sm\:col-span-10 { + grid-column: span 10 / span 10; + } + + .sm\:col-span-11 { + grid-column: span 11 / span 11; + } + + .sm\:col-span-12 { + grid-column: span 12 / span 12; + } + + .sm\:col-start-1 { + grid-column-start: 1; + } + + .sm\:col-start-2 { + grid-column-start: 2; + } + + .sm\:col-start-3 { + grid-column-start: 3; + } + + .sm\:col-start-4 { + grid-column-start: 4; + } + + .sm\:col-start-5 { + grid-column-start: 5; + } + + .sm\:col-start-6 { + grid-column-start: 6; + } + + .sm\:col-start-7 { + grid-column-start: 7; + } + + .sm\:col-start-8 { + grid-column-start: 8; + } + + .sm\:col-start-9 { + grid-column-start: 9; + } + + .sm\:col-start-10 { + grid-column-start: 10; + } + + .sm\:col-start-11 { + grid-column-start: 11; + } + + .sm\:col-start-12 { + grid-column-start: 12; + } + + .sm\:col-start-13 { + grid-column-start: 13; + } + + .sm\:col-start-auto { + grid-column-start: auto; + } + + .sm\:col-end-1 { + grid-column-end: 1; + } + + .sm\:col-end-2 { + grid-column-end: 2; + } + + .sm\:col-end-3 { + grid-column-end: 3; + } + + .sm\:col-end-4 { + grid-column-end: 4; + } + + .sm\:col-end-5 { + grid-column-end: 5; + } + + .sm\:col-end-6 { + grid-column-end: 6; + } + + .sm\:col-end-7 { + grid-column-end: 7; + } + + .sm\:col-end-8 { + grid-column-end: 8; + } + + .sm\:col-end-9 { + grid-column-end: 9; + } + + .sm\:col-end-10 { + grid-column-end: 10; + } + + .sm\:col-end-11 { + grid-column-end: 11; + } + + .sm\:col-end-12 { + grid-column-end: 12; + } + + .sm\:col-end-13 { + grid-column-end: 13; + } + + .sm\:col-end-auto { + grid-column-end: auto; + } + + .sm\:grid-rows-1 { + grid-template-rows: repeat(1, minmax(0, 1fr)); + } + + .sm\:grid-rows-2 { + grid-template-rows: repeat(2, minmax(0, 1fr)); + } + + .sm\:grid-rows-3 { + grid-template-rows: repeat(3, minmax(0, 1fr)); + } + + .sm\:grid-rows-4 { + grid-template-rows: repeat(4, minmax(0, 1fr)); + } + + .sm\:grid-rows-5 { + grid-template-rows: repeat(5, minmax(0, 1fr)); + } + + .sm\:grid-rows-6 { + grid-template-rows: repeat(6, minmax(0, 1fr)); + } + + .sm\:grid-rows-none { + grid-template-rows: none; + } + + .sm\:row-auto { + grid-row: auto; + } + + .sm\:row-span-1 { + grid-row: span 1 / span 1; + } + + .sm\:row-span-2 { + grid-row: span 2 / span 2; + } + + .sm\:row-span-3 { + grid-row: span 3 / span 3; + } + + .sm\:row-span-4 { + grid-row: span 4 / span 4; + } + + .sm\:row-span-5 { + grid-row: span 5 / span 5; + } + + .sm\:row-span-6 { + grid-row: span 6 / span 6; + } + + .sm\:row-start-1 { + grid-row-start: 1; + } + + .sm\:row-start-2 { + grid-row-start: 2; + } + + .sm\:row-start-3 { + grid-row-start: 3; + } + + .sm\:row-start-4 { + grid-row-start: 4; + } + + .sm\:row-start-5 { + grid-row-start: 5; + } + + .sm\:row-start-6 { + grid-row-start: 6; + } + + .sm\:row-start-7 { + grid-row-start: 7; + } + + .sm\:row-start-auto { + grid-row-start: auto; + } + + .sm\:row-end-1 { + grid-row-end: 1; + } + + .sm\:row-end-2 { + grid-row-end: 2; + } + + .sm\:row-end-3 { + grid-row-end: 3; + } + + .sm\:row-end-4 { + grid-row-end: 4; + } + + .sm\:row-end-5 { + grid-row-end: 5; + } + + .sm\:row-end-6 { + grid-row-end: 6; + } + + .sm\:row-end-7 { + grid-row-end: 7; + } + + .sm\:row-end-auto { + grid-row-end: auto; + } + + .sm\:transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); + } + + .sm\:transform-none { + transform: none; + } + + .sm\:origin-center { + transform-origin: center; + } + + .sm\:origin-top { + transform-origin: top; + } + + .sm\:origin-top-right { + transform-origin: top right; + } + + .sm\:origin-right { + transform-origin: right; + } + + .sm\:origin-bottom-right { + transform-origin: bottom right; + } + + .sm\:origin-bottom { + transform-origin: bottom; + } + + .sm\:origin-bottom-left { + transform-origin: bottom left; + } + + .sm\:origin-left { + transform-origin: left; + } + + .sm\:origin-top-left { + transform-origin: top left; + } + + .sm\:scale-0 { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .sm\:scale-50 { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .sm\:scale-75 { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .sm\:scale-90 { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .sm\:scale-95 { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .sm\:scale-100 { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .sm\:scale-105 { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .sm\:scale-110 { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .sm\:scale-125 { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .sm\:scale-150 { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .sm\:scale-x-0 { + --transform-scale-x: 0; + } + + .sm\:scale-x-50 { + --transform-scale-x: .5; + } + + .sm\:scale-x-75 { + --transform-scale-x: .75; + } + + .sm\:scale-x-90 { + --transform-scale-x: .9; + } + + .sm\:scale-x-95 { + --transform-scale-x: .95; + } + + .sm\:scale-x-100 { + --transform-scale-x: 1; + } + + .sm\:scale-x-105 { + --transform-scale-x: 1.05; + } + + .sm\:scale-x-110 { + --transform-scale-x: 1.1; + } + + .sm\:scale-x-125 { + --transform-scale-x: 1.25; + } + + .sm\:scale-x-150 { + --transform-scale-x: 1.5; + } + + .sm\:scale-y-0 { + --transform-scale-y: 0; + } + + .sm\:scale-y-50 { + --transform-scale-y: .5; + } + + .sm\:scale-y-75 { + --transform-scale-y: .75; + } + + .sm\:scale-y-90 { + --transform-scale-y: .9; + } + + .sm\:scale-y-95 { + --transform-scale-y: .95; + } + + .sm\:scale-y-100 { + --transform-scale-y: 1; + } + + .sm\:scale-y-105 { + --transform-scale-y: 1.05; + } + + .sm\:scale-y-110 { + --transform-scale-y: 1.1; + } + + .sm\:scale-y-125 { + --transform-scale-y: 1.25; + } + + .sm\:scale-y-150 { + --transform-scale-y: 1.5; + } + + .sm\:hover\:scale-0:hover { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .sm\:hover\:scale-50:hover { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .sm\:hover\:scale-75:hover { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .sm\:hover\:scale-90:hover { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .sm\:hover\:scale-95:hover { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .sm\:hover\:scale-100:hover { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .sm\:hover\:scale-105:hover { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .sm\:hover\:scale-110:hover { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .sm\:hover\:scale-125:hover { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .sm\:hover\:scale-150:hover { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .sm\:hover\:scale-x-0:hover { + --transform-scale-x: 0; + } + + .sm\:hover\:scale-x-50:hover { + --transform-scale-x: .5; + } + + .sm\:hover\:scale-x-75:hover { + --transform-scale-x: .75; + } + + .sm\:hover\:scale-x-90:hover { + --transform-scale-x: .9; + } + + .sm\:hover\:scale-x-95:hover { + --transform-scale-x: .95; + } + + .sm\:hover\:scale-x-100:hover { + --transform-scale-x: 1; + } + + .sm\:hover\:scale-x-105:hover { + --transform-scale-x: 1.05; + } + + .sm\:hover\:scale-x-110:hover { + --transform-scale-x: 1.1; + } + + .sm\:hover\:scale-x-125:hover { + --transform-scale-x: 1.25; + } + + .sm\:hover\:scale-x-150:hover { + --transform-scale-x: 1.5; + } + + .sm\:hover\:scale-y-0:hover { + --transform-scale-y: 0; + } + + .sm\:hover\:scale-y-50:hover { + --transform-scale-y: .5; + } + + .sm\:hover\:scale-y-75:hover { + --transform-scale-y: .75; + } + + .sm\:hover\:scale-y-90:hover { + --transform-scale-y: .9; + } + + .sm\:hover\:scale-y-95:hover { + --transform-scale-y: .95; + } + + .sm\:hover\:scale-y-100:hover { + --transform-scale-y: 1; + } + + .sm\:hover\:scale-y-105:hover { + --transform-scale-y: 1.05; + } + + .sm\:hover\:scale-y-110:hover { + --transform-scale-y: 1.1; + } + + .sm\:hover\:scale-y-125:hover { + --transform-scale-y: 1.25; + } + + .sm\:hover\:scale-y-150:hover { + --transform-scale-y: 1.5; + } + + .sm\:focus\:scale-0:focus { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .sm\:focus\:scale-50:focus { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .sm\:focus\:scale-75:focus { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .sm\:focus\:scale-90:focus { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .sm\:focus\:scale-95:focus { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .sm\:focus\:scale-100:focus { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .sm\:focus\:scale-105:focus { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .sm\:focus\:scale-110:focus { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .sm\:focus\:scale-125:focus { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .sm\:focus\:scale-150:focus { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .sm\:focus\:scale-x-0:focus { + --transform-scale-x: 0; + } + + .sm\:focus\:scale-x-50:focus { + --transform-scale-x: .5; + } + + .sm\:focus\:scale-x-75:focus { + --transform-scale-x: .75; + } + + .sm\:focus\:scale-x-90:focus { + --transform-scale-x: .9; + } + + .sm\:focus\:scale-x-95:focus { + --transform-scale-x: .95; + } + + .sm\:focus\:scale-x-100:focus { + --transform-scale-x: 1; + } + + .sm\:focus\:scale-x-105:focus { + --transform-scale-x: 1.05; + } + + .sm\:focus\:scale-x-110:focus { + --transform-scale-x: 1.1; + } + + .sm\:focus\:scale-x-125:focus { + --transform-scale-x: 1.25; + } + + .sm\:focus\:scale-x-150:focus { + --transform-scale-x: 1.5; + } + + .sm\:focus\:scale-y-0:focus { + --transform-scale-y: 0; + } + + .sm\:focus\:scale-y-50:focus { + --transform-scale-y: .5; + } + + .sm\:focus\:scale-y-75:focus { + --transform-scale-y: .75; + } + + .sm\:focus\:scale-y-90:focus { + --transform-scale-y: .9; + } + + .sm\:focus\:scale-y-95:focus { + --transform-scale-y: .95; + } + + .sm\:focus\:scale-y-100:focus { + --transform-scale-y: 1; + } + + .sm\:focus\:scale-y-105:focus { + --transform-scale-y: 1.05; + } + + .sm\:focus\:scale-y-110:focus { + --transform-scale-y: 1.1; + } + + .sm\:focus\:scale-y-125:focus { + --transform-scale-y: 1.25; + } + + .sm\:focus\:scale-y-150:focus { + --transform-scale-y: 1.5; + } + + .sm\:rotate-0 { + --transform-rotate: 0; + } + + .sm\:rotate-45 { + --transform-rotate: 45deg; + } + + .sm\:rotate-90 { + --transform-rotate: 90deg; + } + + .sm\:rotate-180 { + --transform-rotate: 180deg; + } + + .sm\:-rotate-180 { + --transform-rotate: -180deg; + } + + .sm\:-rotate-90 { + --transform-rotate: -90deg; + } + + .sm\:-rotate-45 { + --transform-rotate: -45deg; + } + + .sm\:hover\:rotate-0:hover { + --transform-rotate: 0; + } + + .sm\:hover\:rotate-45:hover { + --transform-rotate: 45deg; + } + + .sm\:hover\:rotate-90:hover { + --transform-rotate: 90deg; + } + + .sm\:hover\:rotate-180:hover { + --transform-rotate: 180deg; + } + + .sm\:hover\:-rotate-180:hover { + --transform-rotate: -180deg; + } + + .sm\:hover\:-rotate-90:hover { + --transform-rotate: -90deg; + } + + .sm\:hover\:-rotate-45:hover { + --transform-rotate: -45deg; + } + + .sm\:focus\:rotate-0:focus { + --transform-rotate: 0; + } + + .sm\:focus\:rotate-45:focus { + --transform-rotate: 45deg; + } + + .sm\:focus\:rotate-90:focus { + --transform-rotate: 90deg; + } + + .sm\:focus\:rotate-180:focus { + --transform-rotate: 180deg; + } + + .sm\:focus\:-rotate-180:focus { + --transform-rotate: -180deg; + } + + .sm\:focus\:-rotate-90:focus { + --transform-rotate: -90deg; + } + + .sm\:focus\:-rotate-45:focus { + --transform-rotate: -45deg; + } + + .sm\:translate-x-0 { + --transform-translate-x: 0; + } + + .sm\:translate-x-1 { + --transform-translate-x: 0.25rem; + } + + .sm\:translate-x-2 { + --transform-translate-x: 0.5rem; + } + + .sm\:translate-x-3 { + --transform-translate-x: 0.75rem; + } + + .sm\:translate-x-4 { + --transform-translate-x: 1rem; + } + + .sm\:translate-x-5 { + --transform-translate-x: 1.25rem; + } + + .sm\:translate-x-6 { + --transform-translate-x: 1.5rem; + } + + .sm\:translate-x-8 { + --transform-translate-x: 2rem; + } + + .sm\:translate-x-10 { + --transform-translate-x: 2.5rem; + } + + .sm\:translate-x-12 { + --transform-translate-x: 3rem; + } + + .sm\:translate-x-16 { + --transform-translate-x: 4rem; + } + + .sm\:translate-x-20 { + --transform-translate-x: 5rem; + } + + .sm\:translate-x-24 { + --transform-translate-x: 6rem; + } + + .sm\:translate-x-32 { + --transform-translate-x: 8rem; + } + + .sm\:translate-x-40 { + --transform-translate-x: 10rem; + } + + .sm\:translate-x-48 { + --transform-translate-x: 12rem; + } + + .sm\:translate-x-56 { + --transform-translate-x: 14rem; + } + + .sm\:translate-x-64 { + --transform-translate-x: 16rem; + } + + .sm\:translate-x-px { + --transform-translate-x: 1px; + } + + .sm\:-translate-x-1 { + --transform-translate-x: -0.25rem; + } + + .sm\:-translate-x-2 { + --transform-translate-x: -0.5rem; + } + + .sm\:-translate-x-3 { + --transform-translate-x: -0.75rem; + } + + .sm\:-translate-x-4 { + --transform-translate-x: -1rem; + } + + .sm\:-translate-x-5 { + --transform-translate-x: -1.25rem; + } + + .sm\:-translate-x-6 { + --transform-translate-x: -1.5rem; + } + + .sm\:-translate-x-8 { + --transform-translate-x: -2rem; + } + + .sm\:-translate-x-10 { + --transform-translate-x: -2.5rem; + } + + .sm\:-translate-x-12 { + --transform-translate-x: -3rem; + } + + .sm\:-translate-x-16 { + --transform-translate-x: -4rem; + } + + .sm\:-translate-x-20 { + --transform-translate-x: -5rem; + } + + .sm\:-translate-x-24 { + --transform-translate-x: -6rem; + } + + .sm\:-translate-x-32 { + --transform-translate-x: -8rem; + } + + .sm\:-translate-x-40 { + --transform-translate-x: -10rem; + } + + .sm\:-translate-x-48 { + --transform-translate-x: -12rem; + } + + .sm\:-translate-x-56 { + --transform-translate-x: -14rem; + } + + .sm\:-translate-x-64 { + --transform-translate-x: -16rem; + } + + .sm\:-translate-x-px { + --transform-translate-x: -1px; + } + + .sm\:-translate-x-full { + --transform-translate-x: -100%; + } + + .sm\:-translate-x-1\/2 { + --transform-translate-x: -50%; + } + + .sm\:translate-x-1\/2 { + --transform-translate-x: 50%; + } + + .sm\:translate-x-full { + --transform-translate-x: 100%; + } + + .sm\:translate-y-0 { + --transform-translate-y: 0; + } + + .sm\:translate-y-1 { + --transform-translate-y: 0.25rem; + } + + .sm\:translate-y-2 { + --transform-translate-y: 0.5rem; + } + + .sm\:translate-y-3 { + --transform-translate-y: 0.75rem; + } + + .sm\:translate-y-4 { + --transform-translate-y: 1rem; + } + + .sm\:translate-y-5 { + --transform-translate-y: 1.25rem; + } + + .sm\:translate-y-6 { + --transform-translate-y: 1.5rem; + } + + .sm\:translate-y-8 { + --transform-translate-y: 2rem; + } + + .sm\:translate-y-10 { + --transform-translate-y: 2.5rem; + } + + .sm\:translate-y-12 { + --transform-translate-y: 3rem; + } + + .sm\:translate-y-16 { + --transform-translate-y: 4rem; + } + + .sm\:translate-y-20 { + --transform-translate-y: 5rem; + } + + .sm\:translate-y-24 { + --transform-translate-y: 6rem; + } + + .sm\:translate-y-32 { + --transform-translate-y: 8rem; + } + + .sm\:translate-y-40 { + --transform-translate-y: 10rem; + } + + .sm\:translate-y-48 { + --transform-translate-y: 12rem; + } + + .sm\:translate-y-56 { + --transform-translate-y: 14rem; + } + + .sm\:translate-y-64 { + --transform-translate-y: 16rem; + } + + .sm\:translate-y-px { + --transform-translate-y: 1px; + } + + .sm\:-translate-y-1 { + --transform-translate-y: -0.25rem; + } + + .sm\:-translate-y-2 { + --transform-translate-y: -0.5rem; + } + + .sm\:-translate-y-3 { + --transform-translate-y: -0.75rem; + } + + .sm\:-translate-y-4 { + --transform-translate-y: -1rem; + } + + .sm\:-translate-y-5 { + --transform-translate-y: -1.25rem; + } + + .sm\:-translate-y-6 { + --transform-translate-y: -1.5rem; + } + + .sm\:-translate-y-8 { + --transform-translate-y: -2rem; + } + + .sm\:-translate-y-10 { + --transform-translate-y: -2.5rem; + } + + .sm\:-translate-y-12 { + --transform-translate-y: -3rem; + } + + .sm\:-translate-y-16 { + --transform-translate-y: -4rem; + } + + .sm\:-translate-y-20 { + --transform-translate-y: -5rem; + } + + .sm\:-translate-y-24 { + --transform-translate-y: -6rem; + } + + .sm\:-translate-y-32 { + --transform-translate-y: -8rem; + } + + .sm\:-translate-y-40 { + --transform-translate-y: -10rem; + } + + .sm\:-translate-y-48 { + --transform-translate-y: -12rem; + } + + .sm\:-translate-y-56 { + --transform-translate-y: -14rem; + } + + .sm\:-translate-y-64 { + --transform-translate-y: -16rem; + } + + .sm\:-translate-y-px { + --transform-translate-y: -1px; + } + + .sm\:-translate-y-full { + --transform-translate-y: -100%; + } + + .sm\:-translate-y-1\/2 { + --transform-translate-y: -50%; + } + + .sm\:translate-y-1\/2 { + --transform-translate-y: 50%; + } + + .sm\:translate-y-full { + --transform-translate-y: 100%; + } + + .sm\:hover\:translate-x-0:hover { + --transform-translate-x: 0; + } + + .sm\:hover\:translate-x-1:hover { + --transform-translate-x: 0.25rem; + } + + .sm\:hover\:translate-x-2:hover { + --transform-translate-x: 0.5rem; + } + + .sm\:hover\:translate-x-3:hover { + --transform-translate-x: 0.75rem; + } + + .sm\:hover\:translate-x-4:hover { + --transform-translate-x: 1rem; + } + + .sm\:hover\:translate-x-5:hover { + --transform-translate-x: 1.25rem; + } + + .sm\:hover\:translate-x-6:hover { + --transform-translate-x: 1.5rem; + } + + .sm\:hover\:translate-x-8:hover { + --transform-translate-x: 2rem; + } + + .sm\:hover\:translate-x-10:hover { + --transform-translate-x: 2.5rem; + } + + .sm\:hover\:translate-x-12:hover { + --transform-translate-x: 3rem; + } + + .sm\:hover\:translate-x-16:hover { + --transform-translate-x: 4rem; + } + + .sm\:hover\:translate-x-20:hover { + --transform-translate-x: 5rem; + } + + .sm\:hover\:translate-x-24:hover { + --transform-translate-x: 6rem; + } + + .sm\:hover\:translate-x-32:hover { + --transform-translate-x: 8rem; + } + + .sm\:hover\:translate-x-40:hover { + --transform-translate-x: 10rem; + } + + .sm\:hover\:translate-x-48:hover { + --transform-translate-x: 12rem; + } + + .sm\:hover\:translate-x-56:hover { + --transform-translate-x: 14rem; + } + + .sm\:hover\:translate-x-64:hover { + --transform-translate-x: 16rem; + } + + .sm\:hover\:translate-x-px:hover { + --transform-translate-x: 1px; + } + + .sm\:hover\:-translate-x-1:hover { + --transform-translate-x: -0.25rem; + } + + .sm\:hover\:-translate-x-2:hover { + --transform-translate-x: -0.5rem; + } + + .sm\:hover\:-translate-x-3:hover { + --transform-translate-x: -0.75rem; + } + + .sm\:hover\:-translate-x-4:hover { + --transform-translate-x: -1rem; + } + + .sm\:hover\:-translate-x-5:hover { + --transform-translate-x: -1.25rem; + } + + .sm\:hover\:-translate-x-6:hover { + --transform-translate-x: -1.5rem; + } + + .sm\:hover\:-translate-x-8:hover { + --transform-translate-x: -2rem; + } + + .sm\:hover\:-translate-x-10:hover { + --transform-translate-x: -2.5rem; + } + + .sm\:hover\:-translate-x-12:hover { + --transform-translate-x: -3rem; + } + + .sm\:hover\:-translate-x-16:hover { + --transform-translate-x: -4rem; + } + + .sm\:hover\:-translate-x-20:hover { + --transform-translate-x: -5rem; + } + + .sm\:hover\:-translate-x-24:hover { + --transform-translate-x: -6rem; + } + + .sm\:hover\:-translate-x-32:hover { + --transform-translate-x: -8rem; + } + + .sm\:hover\:-translate-x-40:hover { + --transform-translate-x: -10rem; + } + + .sm\:hover\:-translate-x-48:hover { + --transform-translate-x: -12rem; + } + + .sm\:hover\:-translate-x-56:hover { + --transform-translate-x: -14rem; + } + + .sm\:hover\:-translate-x-64:hover { + --transform-translate-x: -16rem; + } + + .sm\:hover\:-translate-x-px:hover { + --transform-translate-x: -1px; + } + + .sm\:hover\:-translate-x-full:hover { + --transform-translate-x: -100%; + } + + .sm\:hover\:-translate-x-1\/2:hover { + --transform-translate-x: -50%; + } + + .sm\:hover\:translate-x-1\/2:hover { + --transform-translate-x: 50%; + } + + .sm\:hover\:translate-x-full:hover { + --transform-translate-x: 100%; + } + + .sm\:hover\:translate-y-0:hover { + --transform-translate-y: 0; + } + + .sm\:hover\:translate-y-1:hover { + --transform-translate-y: 0.25rem; + } + + .sm\:hover\:translate-y-2:hover { + --transform-translate-y: 0.5rem; + } + + .sm\:hover\:translate-y-3:hover { + --transform-translate-y: 0.75rem; + } + + .sm\:hover\:translate-y-4:hover { + --transform-translate-y: 1rem; + } + + .sm\:hover\:translate-y-5:hover { + --transform-translate-y: 1.25rem; + } + + .sm\:hover\:translate-y-6:hover { + --transform-translate-y: 1.5rem; + } + + .sm\:hover\:translate-y-8:hover { + --transform-translate-y: 2rem; + } + + .sm\:hover\:translate-y-10:hover { + --transform-translate-y: 2.5rem; + } + + .sm\:hover\:translate-y-12:hover { + --transform-translate-y: 3rem; + } + + .sm\:hover\:translate-y-16:hover { + --transform-translate-y: 4rem; + } + + .sm\:hover\:translate-y-20:hover { + --transform-translate-y: 5rem; + } + + .sm\:hover\:translate-y-24:hover { + --transform-translate-y: 6rem; + } + + .sm\:hover\:translate-y-32:hover { + --transform-translate-y: 8rem; + } + + .sm\:hover\:translate-y-40:hover { + --transform-translate-y: 10rem; + } + + .sm\:hover\:translate-y-48:hover { + --transform-translate-y: 12rem; + } + + .sm\:hover\:translate-y-56:hover { + --transform-translate-y: 14rem; + } + + .sm\:hover\:translate-y-64:hover { + --transform-translate-y: 16rem; + } + + .sm\:hover\:translate-y-px:hover { + --transform-translate-y: 1px; + } + + .sm\:hover\:-translate-y-1:hover { + --transform-translate-y: -0.25rem; + } + + .sm\:hover\:-translate-y-2:hover { + --transform-translate-y: -0.5rem; + } + + .sm\:hover\:-translate-y-3:hover { + --transform-translate-y: -0.75rem; + } + + .sm\:hover\:-translate-y-4:hover { + --transform-translate-y: -1rem; + } + + .sm\:hover\:-translate-y-5:hover { + --transform-translate-y: -1.25rem; + } + + .sm\:hover\:-translate-y-6:hover { + --transform-translate-y: -1.5rem; + } + + .sm\:hover\:-translate-y-8:hover { + --transform-translate-y: -2rem; + } + + .sm\:hover\:-translate-y-10:hover { + --transform-translate-y: -2.5rem; + } + + .sm\:hover\:-translate-y-12:hover { + --transform-translate-y: -3rem; + } + + .sm\:hover\:-translate-y-16:hover { + --transform-translate-y: -4rem; + } + + .sm\:hover\:-translate-y-20:hover { + --transform-translate-y: -5rem; + } + + .sm\:hover\:-translate-y-24:hover { + --transform-translate-y: -6rem; + } + + .sm\:hover\:-translate-y-32:hover { + --transform-translate-y: -8rem; + } + + .sm\:hover\:-translate-y-40:hover { + --transform-translate-y: -10rem; + } + + .sm\:hover\:-translate-y-48:hover { + --transform-translate-y: -12rem; + } + + .sm\:hover\:-translate-y-56:hover { + --transform-translate-y: -14rem; + } + + .sm\:hover\:-translate-y-64:hover { + --transform-translate-y: -16rem; + } + + .sm\:hover\:-translate-y-px:hover { + --transform-translate-y: -1px; + } + + .sm\:hover\:-translate-y-full:hover { + --transform-translate-y: -100%; + } + + .sm\:hover\:-translate-y-1\/2:hover { + --transform-translate-y: -50%; + } + + .sm\:hover\:translate-y-1\/2:hover { + --transform-translate-y: 50%; + } + + .sm\:hover\:translate-y-full:hover { + --transform-translate-y: 100%; + } + + .sm\:focus\:translate-x-0:focus { + --transform-translate-x: 0; + } + + .sm\:focus\:translate-x-1:focus { + --transform-translate-x: 0.25rem; + } + + .sm\:focus\:translate-x-2:focus { + --transform-translate-x: 0.5rem; + } + + .sm\:focus\:translate-x-3:focus { + --transform-translate-x: 0.75rem; + } + + .sm\:focus\:translate-x-4:focus { + --transform-translate-x: 1rem; + } + + .sm\:focus\:translate-x-5:focus { + --transform-translate-x: 1.25rem; + } + + .sm\:focus\:translate-x-6:focus { + --transform-translate-x: 1.5rem; + } + + .sm\:focus\:translate-x-8:focus { + --transform-translate-x: 2rem; + } + + .sm\:focus\:translate-x-10:focus { + --transform-translate-x: 2.5rem; + } + + .sm\:focus\:translate-x-12:focus { + --transform-translate-x: 3rem; + } + + .sm\:focus\:translate-x-16:focus { + --transform-translate-x: 4rem; + } + + .sm\:focus\:translate-x-20:focus { + --transform-translate-x: 5rem; + } + + .sm\:focus\:translate-x-24:focus { + --transform-translate-x: 6rem; + } + + .sm\:focus\:translate-x-32:focus { + --transform-translate-x: 8rem; + } + + .sm\:focus\:translate-x-40:focus { + --transform-translate-x: 10rem; + } + + .sm\:focus\:translate-x-48:focus { + --transform-translate-x: 12rem; + } + + .sm\:focus\:translate-x-56:focus { + --transform-translate-x: 14rem; + } + + .sm\:focus\:translate-x-64:focus { + --transform-translate-x: 16rem; + } + + .sm\:focus\:translate-x-px:focus { + --transform-translate-x: 1px; + } + + .sm\:focus\:-translate-x-1:focus { + --transform-translate-x: -0.25rem; + } + + .sm\:focus\:-translate-x-2:focus { + --transform-translate-x: -0.5rem; + } + + .sm\:focus\:-translate-x-3:focus { + --transform-translate-x: -0.75rem; + } + + .sm\:focus\:-translate-x-4:focus { + --transform-translate-x: -1rem; + } + + .sm\:focus\:-translate-x-5:focus { + --transform-translate-x: -1.25rem; + } + + .sm\:focus\:-translate-x-6:focus { + --transform-translate-x: -1.5rem; + } + + .sm\:focus\:-translate-x-8:focus { + --transform-translate-x: -2rem; + } + + .sm\:focus\:-translate-x-10:focus { + --transform-translate-x: -2.5rem; + } + + .sm\:focus\:-translate-x-12:focus { + --transform-translate-x: -3rem; + } + + .sm\:focus\:-translate-x-16:focus { + --transform-translate-x: -4rem; + } + + .sm\:focus\:-translate-x-20:focus { + --transform-translate-x: -5rem; + } + + .sm\:focus\:-translate-x-24:focus { + --transform-translate-x: -6rem; + } + + .sm\:focus\:-translate-x-32:focus { + --transform-translate-x: -8rem; + } + + .sm\:focus\:-translate-x-40:focus { + --transform-translate-x: -10rem; + } + + .sm\:focus\:-translate-x-48:focus { + --transform-translate-x: -12rem; + } + + .sm\:focus\:-translate-x-56:focus { + --transform-translate-x: -14rem; + } + + .sm\:focus\:-translate-x-64:focus { + --transform-translate-x: -16rem; + } + + .sm\:focus\:-translate-x-px:focus { + --transform-translate-x: -1px; + } + + .sm\:focus\:-translate-x-full:focus { + --transform-translate-x: -100%; + } + + .sm\:focus\:-translate-x-1\/2:focus { + --transform-translate-x: -50%; + } + + .sm\:focus\:translate-x-1\/2:focus { + --transform-translate-x: 50%; + } + + .sm\:focus\:translate-x-full:focus { + --transform-translate-x: 100%; + } + + .sm\:focus\:translate-y-0:focus { + --transform-translate-y: 0; + } + + .sm\:focus\:translate-y-1:focus { + --transform-translate-y: 0.25rem; + } + + .sm\:focus\:translate-y-2:focus { + --transform-translate-y: 0.5rem; + } + + .sm\:focus\:translate-y-3:focus { + --transform-translate-y: 0.75rem; + } + + .sm\:focus\:translate-y-4:focus { + --transform-translate-y: 1rem; + } + + .sm\:focus\:translate-y-5:focus { + --transform-translate-y: 1.25rem; + } + + .sm\:focus\:translate-y-6:focus { + --transform-translate-y: 1.5rem; + } + + .sm\:focus\:translate-y-8:focus { + --transform-translate-y: 2rem; + } + + .sm\:focus\:translate-y-10:focus { + --transform-translate-y: 2.5rem; + } + + .sm\:focus\:translate-y-12:focus { + --transform-translate-y: 3rem; + } + + .sm\:focus\:translate-y-16:focus { + --transform-translate-y: 4rem; + } + + .sm\:focus\:translate-y-20:focus { + --transform-translate-y: 5rem; + } + + .sm\:focus\:translate-y-24:focus { + --transform-translate-y: 6rem; + } + + .sm\:focus\:translate-y-32:focus { + --transform-translate-y: 8rem; + } + + .sm\:focus\:translate-y-40:focus { + --transform-translate-y: 10rem; + } + + .sm\:focus\:translate-y-48:focus { + --transform-translate-y: 12rem; + } + + .sm\:focus\:translate-y-56:focus { + --transform-translate-y: 14rem; + } + + .sm\:focus\:translate-y-64:focus { + --transform-translate-y: 16rem; + } + + .sm\:focus\:translate-y-px:focus { + --transform-translate-y: 1px; + } + + .sm\:focus\:-translate-y-1:focus { + --transform-translate-y: -0.25rem; + } + + .sm\:focus\:-translate-y-2:focus { + --transform-translate-y: -0.5rem; + } + + .sm\:focus\:-translate-y-3:focus { + --transform-translate-y: -0.75rem; + } + + .sm\:focus\:-translate-y-4:focus { + --transform-translate-y: -1rem; + } + + .sm\:focus\:-translate-y-5:focus { + --transform-translate-y: -1.25rem; + } + + .sm\:focus\:-translate-y-6:focus { + --transform-translate-y: -1.5rem; + } + + .sm\:focus\:-translate-y-8:focus { + --transform-translate-y: -2rem; + } + + .sm\:focus\:-translate-y-10:focus { + --transform-translate-y: -2.5rem; + } + + .sm\:focus\:-translate-y-12:focus { + --transform-translate-y: -3rem; + } + + .sm\:focus\:-translate-y-16:focus { + --transform-translate-y: -4rem; + } + + .sm\:focus\:-translate-y-20:focus { + --transform-translate-y: -5rem; + } + + .sm\:focus\:-translate-y-24:focus { + --transform-translate-y: -6rem; + } + + .sm\:focus\:-translate-y-32:focus { + --transform-translate-y: -8rem; + } + + .sm\:focus\:-translate-y-40:focus { + --transform-translate-y: -10rem; + } + + .sm\:focus\:-translate-y-48:focus { + --transform-translate-y: -12rem; + } + + .sm\:focus\:-translate-y-56:focus { + --transform-translate-y: -14rem; + } + + .sm\:focus\:-translate-y-64:focus { + --transform-translate-y: -16rem; + } + + .sm\:focus\:-translate-y-px:focus { + --transform-translate-y: -1px; + } + + .sm\:focus\:-translate-y-full:focus { + --transform-translate-y: -100%; + } + + .sm\:focus\:-translate-y-1\/2:focus { + --transform-translate-y: -50%; + } + + .sm\:focus\:translate-y-1\/2:focus { + --transform-translate-y: 50%; + } + + .sm\:focus\:translate-y-full:focus { + --transform-translate-y: 100%; + } + + .sm\:skew-x-0 { + --transform-skew-x: 0; + } + + .sm\:skew-x-3 { + --transform-skew-x: 3deg; + } + + .sm\:skew-x-6 { + --transform-skew-x: 6deg; + } + + .sm\:skew-x-12 { + --transform-skew-x: 12deg; + } + + .sm\:-skew-x-12 { + --transform-skew-x: -12deg; + } + + .sm\:-skew-x-6 { + --transform-skew-x: -6deg; + } + + .sm\:-skew-x-3 { + --transform-skew-x: -3deg; + } + + .sm\:skew-y-0 { + --transform-skew-y: 0; + } + + .sm\:skew-y-3 { + --transform-skew-y: 3deg; + } + + .sm\:skew-y-6 { + --transform-skew-y: 6deg; + } + + .sm\:skew-y-12 { + --transform-skew-y: 12deg; + } + + .sm\:-skew-y-12 { + --transform-skew-y: -12deg; + } + + .sm\:-skew-y-6 { + --transform-skew-y: -6deg; + } + + .sm\:-skew-y-3 { + --transform-skew-y: -3deg; + } + + .sm\:hover\:skew-x-0:hover { + --transform-skew-x: 0; + } + + .sm\:hover\:skew-x-3:hover { + --transform-skew-x: 3deg; + } + + .sm\:hover\:skew-x-6:hover { + --transform-skew-x: 6deg; + } + + .sm\:hover\:skew-x-12:hover { + --transform-skew-x: 12deg; + } + + .sm\:hover\:-skew-x-12:hover { + --transform-skew-x: -12deg; + } + + .sm\:hover\:-skew-x-6:hover { + --transform-skew-x: -6deg; + } + + .sm\:hover\:-skew-x-3:hover { + --transform-skew-x: -3deg; + } + + .sm\:hover\:skew-y-0:hover { + --transform-skew-y: 0; + } + + .sm\:hover\:skew-y-3:hover { + --transform-skew-y: 3deg; + } + + .sm\:hover\:skew-y-6:hover { + --transform-skew-y: 6deg; + } + + .sm\:hover\:skew-y-12:hover { + --transform-skew-y: 12deg; + } + + .sm\:hover\:-skew-y-12:hover { + --transform-skew-y: -12deg; + } + + .sm\:hover\:-skew-y-6:hover { + --transform-skew-y: -6deg; + } + + .sm\:hover\:-skew-y-3:hover { + --transform-skew-y: -3deg; + } + + .sm\:focus\:skew-x-0:focus { + --transform-skew-x: 0; + } + + .sm\:focus\:skew-x-3:focus { + --transform-skew-x: 3deg; + } + + .sm\:focus\:skew-x-6:focus { + --transform-skew-x: 6deg; + } + + .sm\:focus\:skew-x-12:focus { + --transform-skew-x: 12deg; + } + + .sm\:focus\:-skew-x-12:focus { + --transform-skew-x: -12deg; + } + + .sm\:focus\:-skew-x-6:focus { + --transform-skew-x: -6deg; + } + + .sm\:focus\:-skew-x-3:focus { + --transform-skew-x: -3deg; + } + + .sm\:focus\:skew-y-0:focus { + --transform-skew-y: 0; + } + + .sm\:focus\:skew-y-3:focus { + --transform-skew-y: 3deg; + } + + .sm\:focus\:skew-y-6:focus { + --transform-skew-y: 6deg; + } + + .sm\:focus\:skew-y-12:focus { + --transform-skew-y: 12deg; + } + + .sm\:focus\:-skew-y-12:focus { + --transform-skew-y: -12deg; + } + + .sm\:focus\:-skew-y-6:focus { + --transform-skew-y: -6deg; + } + + .sm\:focus\:-skew-y-3:focus { + --transform-skew-y: -3deg; + } + + .sm\:transition-none { + transition-property: none; + } + + .sm\:transition-all { + transition-property: all; + } + + .sm\:transition { + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; + } + + .sm\:transition-colors { + transition-property: background-color, border-color, color, fill, stroke; + } + + .sm\:transition-opacity { + transition-property: opacity; + } + + .sm\:transition-shadow { + transition-property: box-shadow; + } + + .sm\:transition-transform { + transition-property: transform; + } + + .sm\:ease-linear { + transition-timing-function: linear; + } + + .sm\:ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); + } + + .sm\:ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + + .sm\:ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .sm\:duration-75 { + transition-duration: 75ms; + } + + .sm\:duration-100 { + transition-duration: 100ms; + } + + .sm\:duration-150 { + transition-duration: 150ms; + } + + .sm\:duration-200 { + transition-duration: 200ms; + } + + .sm\:duration-300 { + transition-duration: 300ms; + } + + .sm\:duration-500 { + transition-duration: 500ms; + } + + .sm\:duration-700 { + transition-duration: 700ms; + } + + .sm\:duration-1000 { + transition-duration: 1000ms; + } + + .sm\:delay-75 { + transition-delay: 75ms; + } + + .sm\:delay-100 { + transition-delay: 100ms; + } + + .sm\:delay-150 { + transition-delay: 150ms; + } + + .sm\:delay-200 { + transition-delay: 200ms; + } + + .sm\:delay-300 { + transition-delay: 300ms; + } + + .sm\:delay-500 { + transition-delay: 500ms; + } + + .sm\:delay-700 { + transition-delay: 700ms; + } + + .sm\:delay-1000 { + transition-delay: 1000ms; + } + + .sm\:animate-none { + -webkit-animation: none; + animation: none; + } + + .sm\:animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + } + + .sm\:animate-ping { + -webkit-animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + } + + .sm\:animate-pulse { + -webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + .sm\:animate-bounce { + -webkit-animation: bounce 1s infinite; + animation: bounce 1s infinite; + } +} + +@media (min-width: 768px) { + .md\:container { + width: 100%; + } + + @media (min-width: 640px) { + .md\:container { + max-width: 640px; + } + } + + @media (min-width: 768px) { + .md\:container { + max-width: 768px; + } + } + + @media (min-width: 1024px) { + .md\:container { + max-width: 1024px; + } + } + + @media (min-width: 1280px) { + .md\:container { + max-width: 1280px; + } + } + + .md\:space-y-0 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0px * var(--space-y-reverse)); + } + + .md\:space-x-0 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0px * var(--space-x-reverse)); + margin-left: calc(0px * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.25rem * var(--space-y-reverse)); + } + + .md\:space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.25rem * var(--space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.5rem * var(--space-y-reverse)); + } + + .md\:space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.5rem * var(--space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.75rem * var(--space-y-reverse)); + } + + .md\:space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.75rem * var(--space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1rem * var(--space-y-reverse)); + } + + .md\:space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1rem * var(--space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.25rem * var(--space-y-reverse)); + } + + .md\:space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.25rem * var(--space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.5rem * var(--space-y-reverse)); + } + + .md\:space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.5rem * var(--space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2rem * var(--space-y-reverse)); + } + + .md\:space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2rem * var(--space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2.5rem * var(--space-y-reverse)); + } + + .md\:space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2.5rem * var(--space-x-reverse)); + margin-left: calc(2.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(3rem * var(--space-y-reverse)); + } + + .md\:space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(3rem * var(--space-x-reverse)); + margin-left: calc(3rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(4rem * var(--space-y-reverse)); + } + + .md\:space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(4rem * var(--space-x-reverse)); + margin-left: calc(4rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(5rem * var(--space-y-reverse)); + } + + .md\:space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(5rem * var(--space-x-reverse)); + margin-left: calc(5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(6rem * var(--space-y-reverse)); + } + + .md\:space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(6rem * var(--space-x-reverse)); + margin-left: calc(6rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(8rem * var(--space-y-reverse)); + } + + .md\:space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(8rem * var(--space-x-reverse)); + margin-left: calc(8rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(10rem * var(--space-y-reverse)); + } + + .md\:space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(10rem * var(--space-x-reverse)); + margin-left: calc(10rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(12rem * var(--space-y-reverse)); + } + + .md\:space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(12rem * var(--space-x-reverse)); + margin-left: calc(12rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(14rem * var(--space-y-reverse)); + } + + .md\:space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(14rem * var(--space-x-reverse)); + margin-left: calc(14rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(16rem * var(--space-y-reverse)); + } + + .md\:space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(16rem * var(--space-x-reverse)); + margin-left: calc(16rem * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1px * var(--space-y-reverse)); + } + + .md\:space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1px * var(--space-x-reverse)); + margin-left: calc(1px * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.25rem * var(--space-y-reverse)); + } + + .md\:-space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.25rem * var(--space-x-reverse)); + margin-left: calc(-0.25rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.5rem * var(--space-y-reverse)); + } + + .md\:-space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.5rem * var(--space-x-reverse)); + margin-left: calc(-0.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.75rem * var(--space-y-reverse)); + } + + .md\:-space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.75rem * var(--space-x-reverse)); + margin-left: calc(-0.75rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1rem * var(--space-y-reverse)); + } + + .md\:-space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1rem * var(--space-x-reverse)); + margin-left: calc(-1rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.25rem * var(--space-y-reverse)); + } + + .md\:-space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.25rem * var(--space-x-reverse)); + margin-left: calc(-1.25rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.5rem * var(--space-y-reverse)); + } + + .md\:-space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.5rem * var(--space-x-reverse)); + margin-left: calc(-1.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2rem * var(--space-y-reverse)); + } + + .md\:-space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2rem * var(--space-x-reverse)); + margin-left: calc(-2rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2.5rem * var(--space-y-reverse)); + } + + .md\:-space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2.5rem * var(--space-x-reverse)); + margin-left: calc(-2.5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-3rem * var(--space-y-reverse)); + } + + .md\:-space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-3rem * var(--space-x-reverse)); + margin-left: calc(-3rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-4rem * var(--space-y-reverse)); + } + + .md\:-space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-4rem * var(--space-x-reverse)); + margin-left: calc(-4rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-5rem * var(--space-y-reverse)); + } + + .md\:-space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-5rem * var(--space-x-reverse)); + margin-left: calc(-5rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-6rem * var(--space-y-reverse)); + } + + .md\:-space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-6rem * var(--space-x-reverse)); + margin-left: calc(-6rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-8rem * var(--space-y-reverse)); + } + + .md\:-space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-8rem * var(--space-x-reverse)); + margin-left: calc(-8rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-10rem * var(--space-y-reverse)); + } + + .md\:-space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-10rem * var(--space-x-reverse)); + margin-left: calc(-10rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-12rem * var(--space-y-reverse)); + } + + .md\:-space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-12rem * var(--space-x-reverse)); + margin-left: calc(-12rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-14rem * var(--space-y-reverse)); + } + + .md\:-space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-14rem * var(--space-x-reverse)); + margin-left: calc(-14rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-16rem * var(--space-y-reverse)); + } + + .md\:-space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-16rem * var(--space-x-reverse)); + margin-left: calc(-16rem * calc(1 - var(--space-x-reverse))); + } + + .md\:-space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1px * var(--space-y-reverse)); + } + + .md\:-space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1px * var(--space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--space-x-reverse))); + } + + .md\:space-y-reverse > :not(template) ~ :not(template) { + --space-y-reverse: 1; + } + + .md\:space-x-reverse > :not(template) ~ :not(template) { + --space-x-reverse: 1; + } + + .md\:divide-y-0 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(0px * var(--divide-y-reverse)); + } + + .md\:divide-x-0 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(0px * var(--divide-x-reverse)); + border-left-width: calc(0px * calc(1 - var(--divide-x-reverse))); + } + + .md\:divide-y-2 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(2px * var(--divide-y-reverse)); + } + + .md\:divide-x-2 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(2px * var(--divide-x-reverse)); + border-left-width: calc(2px * calc(1 - var(--divide-x-reverse))); + } + + .md\:divide-y-4 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(4px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(4px * var(--divide-y-reverse)); + } + + .md\:divide-x-4 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(4px * var(--divide-x-reverse)); + border-left-width: calc(4px * calc(1 - var(--divide-x-reverse))); + } + + .md\:divide-y-8 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(8px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(8px * var(--divide-y-reverse)); + } + + .md\:divide-x-8 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(8px * var(--divide-x-reverse)); + border-left-width: calc(8px * calc(1 - var(--divide-x-reverse))); + } + + .md\:divide-y > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(1px * var(--divide-y-reverse)); + } + + .md\:divide-x > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(1px * var(--divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--divide-x-reverse))); + } + + .md\:divide-y-reverse > :not(template) ~ :not(template) { + --divide-y-reverse: 1; + } + + .md\:divide-x-reverse > :not(template) ~ :not(template) { + --divide-x-reverse: 1; + } + + .md\:divide-transparent > :not(template) ~ :not(template) { + border-color: transparent; + } + + .md\:divide-current > :not(template) ~ :not(template) { + border-color: currentColor; + } + + .md\:divide-black > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--divide-opacity)); + } + + .md\:divide-white > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--divide-opacity)); + } + + .md\:divide-gray-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--divide-opacity)); + } + + .md\:divide-gray-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--divide-opacity)); + } + + .md\:divide-gray-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--divide-opacity)); + } + + .md\:divide-gray-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--divide-opacity)); + } + + .md\:divide-gray-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--divide-opacity)); + } + + .md\:divide-gray-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--divide-opacity)); + } + + .md\:divide-gray-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--divide-opacity)); + } + + .md\:divide-gray-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--divide-opacity)); + } + + .md\:divide-gray-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--divide-opacity)); + } + + .md\:divide-red-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--divide-opacity)); + } + + .md\:divide-red-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--divide-opacity)); + } + + .md\:divide-red-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--divide-opacity)); + } + + .md\:divide-red-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--divide-opacity)); + } + + .md\:divide-red-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--divide-opacity)); + } + + .md\:divide-red-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--divide-opacity)); + } + + .md\:divide-red-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--divide-opacity)); + } + + .md\:divide-red-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--divide-opacity)); + } + + .md\:divide-red-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--divide-opacity)); + } + + .md\:divide-orange-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--divide-opacity)); + } + + .md\:divide-orange-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--divide-opacity)); + } + + .md\:divide-orange-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--divide-opacity)); + } + + .md\:divide-orange-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--divide-opacity)); + } + + .md\:divide-orange-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--divide-opacity)); + } + + .md\:divide-orange-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--divide-opacity)); + } + + .md\:divide-orange-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--divide-opacity)); + } + + .md\:divide-orange-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--divide-opacity)); + } + + .md\:divide-orange-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--divide-opacity)); + } + + .md\:divide-yellow-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--divide-opacity)); + } + + .md\:divide-yellow-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--divide-opacity)); + } + + .md\:divide-yellow-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--divide-opacity)); + } + + .md\:divide-yellow-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--divide-opacity)); + } + + .md\:divide-yellow-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--divide-opacity)); + } + + .md\:divide-yellow-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--divide-opacity)); + } + + .md\:divide-yellow-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--divide-opacity)); + } + + .md\:divide-yellow-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--divide-opacity)); + } + + .md\:divide-yellow-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--divide-opacity)); + } + + .md\:divide-green-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--divide-opacity)); + } + + .md\:divide-green-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--divide-opacity)); + } + + .md\:divide-green-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--divide-opacity)); + } + + .md\:divide-green-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--divide-opacity)); + } + + .md\:divide-green-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--divide-opacity)); + } + + .md\:divide-green-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--divide-opacity)); + } + + .md\:divide-green-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--divide-opacity)); + } + + .md\:divide-green-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--divide-opacity)); + } + + .md\:divide-green-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--divide-opacity)); + } + + .md\:divide-teal-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--divide-opacity)); + } + + .md\:divide-teal-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--divide-opacity)); + } + + .md\:divide-teal-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--divide-opacity)); + } + + .md\:divide-teal-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--divide-opacity)); + } + + .md\:divide-teal-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--divide-opacity)); + } + + .md\:divide-teal-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--divide-opacity)); + } + + .md\:divide-teal-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--divide-opacity)); + } + + .md\:divide-teal-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--divide-opacity)); + } + + .md\:divide-teal-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--divide-opacity)); + } + + .md\:divide-blue-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--divide-opacity)); + } + + .md\:divide-blue-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--divide-opacity)); + } + + .md\:divide-blue-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--divide-opacity)); + } + + .md\:divide-blue-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--divide-opacity)); + } + + .md\:divide-blue-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--divide-opacity)); + } + + .md\:divide-blue-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--divide-opacity)); + } + + .md\:divide-blue-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--divide-opacity)); + } + + .md\:divide-blue-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--divide-opacity)); + } + + .md\:divide-blue-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--divide-opacity)); + } + + .md\:divide-indigo-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--divide-opacity)); + } + + .md\:divide-indigo-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--divide-opacity)); + } + + .md\:divide-indigo-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--divide-opacity)); + } + + .md\:divide-indigo-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--divide-opacity)); + } + + .md\:divide-indigo-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--divide-opacity)); + } + + .md\:divide-indigo-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--divide-opacity)); + } + + .md\:divide-indigo-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--divide-opacity)); + } + + .md\:divide-indigo-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--divide-opacity)); + } + + .md\:divide-indigo-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--divide-opacity)); + } + + .md\:divide-purple-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--divide-opacity)); + } + + .md\:divide-purple-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--divide-opacity)); + } + + .md\:divide-purple-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--divide-opacity)); + } + + .md\:divide-purple-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--divide-opacity)); + } + + .md\:divide-purple-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--divide-opacity)); + } + + .md\:divide-purple-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--divide-opacity)); + } + + .md\:divide-purple-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--divide-opacity)); + } + + .md\:divide-purple-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--divide-opacity)); + } + + .md\:divide-purple-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--divide-opacity)); + } + + .md\:divide-pink-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--divide-opacity)); + } + + .md\:divide-pink-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--divide-opacity)); + } + + .md\:divide-pink-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--divide-opacity)); + } + + .md\:divide-pink-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--divide-opacity)); + } + + .md\:divide-pink-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--divide-opacity)); + } + + .md\:divide-pink-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--divide-opacity)); + } + + .md\:divide-pink-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--divide-opacity)); + } + + .md\:divide-pink-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--divide-opacity)); + } + + .md\:divide-pink-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--divide-opacity)); + } + + .md\:divide-solid > :not(template) ~ :not(template) { + border-style: solid; + } + + .md\:divide-dashed > :not(template) ~ :not(template) { + border-style: dashed; + } + + .md\:divide-dotted > :not(template) ~ :not(template) { + border-style: dotted; + } + + .md\:divide-double > :not(template) ~ :not(template) { + border-style: double; + } + + .md\:divide-none > :not(template) ~ :not(template) { + border-style: none; + } + + .md\:divide-opacity-0 > :not(template) ~ :not(template) { + --divide-opacity: 0; + } + + .md\:divide-opacity-25 > :not(template) ~ :not(template) { + --divide-opacity: 0.25; + } + + .md\:divide-opacity-50 > :not(template) ~ :not(template) { + --divide-opacity: 0.5; + } + + .md\:divide-opacity-75 > :not(template) ~ :not(template) { + --divide-opacity: 0.75; + } + + .md\:divide-opacity-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + } + + .md\:sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .md\:not-sr-only { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .md\:focus\:sr-only:focus { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .md\:focus\:not-sr-only:focus { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .md\:appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + .md\:bg-fixed { + background-attachment: fixed; + } + + .md\:bg-local { + background-attachment: local; + } + + .md\:bg-scroll { + background-attachment: scroll; + } + + .md\:bg-clip-border { + background-clip: border-box; + } + + .md\:bg-clip-padding { + background-clip: padding-box; + } + + .md\:bg-clip-content { + background-clip: content-box; + } + + .md\:bg-clip-text { + -webkit-background-clip: text; + background-clip: text; + } + + .md\:bg-transparent { + background-color: transparent; + } + + .md\:bg-current { + background-color: currentColor; + } + + .md\:bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .md\:bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .md\:bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .md\:bg-gray-200 { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .md\:bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .md\:bg-gray-400 { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .md\:bg-gray-500 { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .md\:bg-gray-600 { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .md\:bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .md\:bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .md\:bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .md\:bg-red-100 { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .md\:bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .md\:bg-red-300 { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .md\:bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .md\:bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .md\:bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .md\:bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .md\:bg-red-800 { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .md\:bg-red-900 { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .md\:bg-orange-100 { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .md\:bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .md\:bg-orange-300 { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .md\:bg-orange-400 { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .md\:bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .md\:bg-orange-600 { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .md\:bg-orange-700 { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .md\:bg-orange-800 { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .md\:bg-orange-900 { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .md\:bg-yellow-100 { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .md\:bg-yellow-200 { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .md\:bg-yellow-300 { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .md\:bg-yellow-400 { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .md\:bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .md\:bg-yellow-600 { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .md\:bg-yellow-700 { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .md\:bg-yellow-800 { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .md\:bg-yellow-900 { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .md\:bg-green-100 { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .md\:bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .md\:bg-green-300 { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .md\:bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .md\:bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .md\:bg-green-600 { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .md\:bg-green-700 { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .md\:bg-green-800 { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .md\:bg-green-900 { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .md\:bg-teal-100 { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .md\:bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .md\:bg-teal-300 { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .md\:bg-teal-400 { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .md\:bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .md\:bg-teal-600 { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .md\:bg-teal-700 { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .md\:bg-teal-800 { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .md\:bg-teal-900 { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .md\:bg-blue-100 { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .md\:bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .md\:bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .md\:bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .md\:bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .md\:bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .md\:bg-blue-700 { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .md\:bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .md\:bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .md\:bg-indigo-100 { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .md\:bg-indigo-200 { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .md\:bg-indigo-300 { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .md\:bg-indigo-400 { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .md\:bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .md\:bg-indigo-600 { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .md\:bg-indigo-700 { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .md\:bg-indigo-800 { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .md\:bg-indigo-900 { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .md\:bg-purple-100 { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .md\:bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .md\:bg-purple-300 { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .md\:bg-purple-400 { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .md\:bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .md\:bg-purple-600 { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .md\:bg-purple-700 { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .md\:bg-purple-800 { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .md\:bg-purple-900 { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .md\:bg-pink-100 { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .md\:bg-pink-200 { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .md\:bg-pink-300 { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .md\:bg-pink-400 { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .md\:bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .md\:bg-pink-600 { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .md\:bg-pink-700 { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .md\:bg-pink-800 { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .md\:bg-pink-900 { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .md\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .md\:hover\:bg-current:hover { + background-color: currentColor; + } + + .md\:hover\:bg-black:hover { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .md\:hover\:bg-white:hover { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-100:hover { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-200:hover { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-300:hover { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-400:hover { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-500:hover { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-600:hover { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-700:hover { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-800:hover { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .md\:hover\:bg-gray-900:hover { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .md\:hover\:bg-red-100:hover { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .md\:hover\:bg-red-200:hover { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .md\:hover\:bg-red-300:hover { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .md\:hover\:bg-red-400:hover { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .md\:hover\:bg-red-500:hover { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .md\:hover\:bg-red-600:hover { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .md\:hover\:bg-red-700:hover { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .md\:hover\:bg-red-800:hover { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .md\:hover\:bg-red-900:hover { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-100:hover { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-200:hover { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-300:hover { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-400:hover { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-500:hover { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-600:hover { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-700:hover { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-800:hover { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .md\:hover\:bg-orange-900:hover { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-100:hover { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-200:hover { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-300:hover { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-400:hover { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-500:hover { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-600:hover { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-700:hover { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-800:hover { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .md\:hover\:bg-yellow-900:hover { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .md\:hover\:bg-green-100:hover { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .md\:hover\:bg-green-200:hover { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .md\:hover\:bg-green-300:hover { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .md\:hover\:bg-green-400:hover { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .md\:hover\:bg-green-500:hover { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .md\:hover\:bg-green-600:hover { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .md\:hover\:bg-green-700:hover { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .md\:hover\:bg-green-800:hover { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .md\:hover\:bg-green-900:hover { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-100:hover { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-200:hover { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-300:hover { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-400:hover { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-500:hover { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-600:hover { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-700:hover { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-800:hover { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .md\:hover\:bg-teal-900:hover { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-100:hover { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-200:hover { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-300:hover { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-400:hover { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-500:hover { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-600:hover { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-700:hover { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-800:hover { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .md\:hover\:bg-blue-900:hover { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-100:hover { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-200:hover { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-300:hover { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-400:hover { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-500:hover { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-600:hover { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-700:hover { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-800:hover { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .md\:hover\:bg-indigo-900:hover { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-100:hover { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-200:hover { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-300:hover { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-400:hover { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-500:hover { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-600:hover { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-700:hover { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-800:hover { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .md\:hover\:bg-purple-900:hover { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-100:hover { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-200:hover { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-300:hover { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-400:hover { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-500:hover { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-600:hover { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-700:hover { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-800:hover { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .md\:hover\:bg-pink-900:hover { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .md\:focus\:bg-transparent:focus { + background-color: transparent; + } + + .md\:focus\:bg-current:focus { + background-color: currentColor; + } + + .md\:focus\:bg-black:focus { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .md\:focus\:bg-white:focus { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-100:focus { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-200:focus { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-300:focus { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-400:focus { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-500:focus { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-600:focus { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-700:focus { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-800:focus { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .md\:focus\:bg-gray-900:focus { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .md\:focus\:bg-red-100:focus { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .md\:focus\:bg-red-200:focus { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .md\:focus\:bg-red-300:focus { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .md\:focus\:bg-red-400:focus { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .md\:focus\:bg-red-500:focus { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .md\:focus\:bg-red-600:focus { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .md\:focus\:bg-red-700:focus { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .md\:focus\:bg-red-800:focus { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .md\:focus\:bg-red-900:focus { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-100:focus { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-200:focus { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-300:focus { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-400:focus { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-500:focus { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-600:focus { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-700:focus { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-800:focus { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .md\:focus\:bg-orange-900:focus { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-100:focus { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-200:focus { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-300:focus { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-400:focus { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-500:focus { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-600:focus { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-700:focus { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-800:focus { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .md\:focus\:bg-yellow-900:focus { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .md\:focus\:bg-green-100:focus { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .md\:focus\:bg-green-200:focus { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .md\:focus\:bg-green-300:focus { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .md\:focus\:bg-green-400:focus { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .md\:focus\:bg-green-500:focus { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .md\:focus\:bg-green-600:focus { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .md\:focus\:bg-green-700:focus { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .md\:focus\:bg-green-800:focus { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .md\:focus\:bg-green-900:focus { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-100:focus { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-200:focus { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-300:focus { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-400:focus { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-500:focus { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-600:focus { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-700:focus { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-800:focus { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .md\:focus\:bg-teal-900:focus { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-100:focus { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-200:focus { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-300:focus { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-400:focus { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-500:focus { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-600:focus { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-700:focus { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-800:focus { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .md\:focus\:bg-blue-900:focus { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-100:focus { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-200:focus { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-300:focus { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-400:focus { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-500:focus { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-600:focus { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-700:focus { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-800:focus { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .md\:focus\:bg-indigo-900:focus { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-100:focus { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-200:focus { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-300:focus { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-400:focus { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-500:focus { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-600:focus { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-700:focus { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-800:focus { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .md\:focus\:bg-purple-900:focus { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-100:focus { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-200:focus { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-300:focus { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-400:focus { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-500:focus { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-600:focus { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-700:focus { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-800:focus { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .md\:focus\:bg-pink-900:focus { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .md\:bg-none { + background-image: none; + } + + .md\:bg-gradient-to-t { + background-image: linear-gradient(to top, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-r { + background-image: linear-gradient(to right, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-l { + background-image: linear-gradient(to left, var(--gradient-color-stops)); + } + + .md\:bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--gradient-color-stops)); + } + + .md\:from-transparent { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:from-current { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:from-black { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:from-white { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:from-gray-100 { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:from-gray-200 { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:from-gray-300 { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:from-gray-400 { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:from-gray-500 { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:from-gray-600 { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:from-gray-700 { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:from-gray-800 { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:from-gray-900 { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:from-red-100 { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:from-red-200 { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:from-red-300 { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:from-red-400 { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:from-red-500 { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:from-red-600 { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:from-red-700 { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:from-red-800 { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:from-red-900 { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:from-orange-100 { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:from-orange-200 { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:from-orange-300 { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:from-orange-400 { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:from-orange-500 { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:from-orange-600 { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:from-orange-700 { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:from-orange-800 { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:from-orange-900 { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:from-yellow-100 { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:from-yellow-200 { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:from-yellow-300 { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:from-yellow-400 { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:from-yellow-500 { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:from-yellow-600 { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:from-yellow-700 { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:from-yellow-800 { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:from-yellow-900 { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:from-green-100 { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:from-green-200 { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:from-green-300 { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:from-green-400 { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:from-green-500 { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:from-green-600 { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:from-green-700 { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:from-green-800 { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:from-green-900 { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:from-teal-100 { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:from-teal-200 { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:from-teal-300 { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:from-teal-400 { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:from-teal-500 { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:from-teal-600 { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:from-teal-700 { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:from-teal-800 { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:from-teal-900 { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:from-blue-100 { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:from-blue-200 { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:from-blue-300 { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:from-blue-400 { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:from-blue-500 { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:from-blue-600 { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:from-blue-700 { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:from-blue-800 { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:from-blue-900 { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:from-indigo-100 { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:from-indigo-200 { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:from-indigo-300 { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:from-indigo-400 { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:from-indigo-500 { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:from-indigo-600 { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:from-indigo-700 { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:from-indigo-800 { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:from-indigo-900 { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:from-purple-100 { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:from-purple-200 { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:from-purple-300 { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:from-purple-400 { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:from-purple-500 { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:from-purple-600 { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:from-purple-700 { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:from-purple-800 { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:from-purple-900 { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:from-pink-100 { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:from-pink-200 { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:from-pink-300 { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:from-pink-400 { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:from-pink-500 { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:from-pink-600 { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:from-pink-700 { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:from-pink-800 { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:from-pink-900 { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:via-transparent { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:via-current { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:via-black { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:via-white { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:via-gray-100 { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:via-gray-200 { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:via-gray-300 { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:via-gray-400 { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:via-gray-500 { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:via-gray-600 { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:via-gray-700 { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:via-gray-800 { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:via-gray-900 { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:via-red-100 { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:via-red-200 { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:via-red-300 { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:via-red-400 { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:via-red-500 { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:via-red-600 { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:via-red-700 { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:via-red-800 { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:via-red-900 { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:via-orange-100 { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:via-orange-200 { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:via-orange-300 { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:via-orange-400 { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:via-orange-500 { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:via-orange-600 { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:via-orange-700 { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:via-orange-800 { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:via-orange-900 { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:via-yellow-100 { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:via-yellow-200 { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:via-yellow-300 { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:via-yellow-400 { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:via-yellow-500 { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:via-yellow-600 { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:via-yellow-700 { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:via-yellow-800 { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:via-yellow-900 { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:via-green-100 { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:via-green-200 { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:via-green-300 { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:via-green-400 { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:via-green-500 { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:via-green-600 { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:via-green-700 { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:via-green-800 { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:via-green-900 { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:via-teal-100 { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:via-teal-200 { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:via-teal-300 { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:via-teal-400 { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:via-teal-500 { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:via-teal-600 { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:via-teal-700 { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:via-teal-800 { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:via-teal-900 { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:via-blue-100 { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:via-blue-200 { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:via-blue-300 { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:via-blue-400 { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:via-blue-500 { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:via-blue-600 { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:via-blue-700 { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:via-blue-800 { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:via-blue-900 { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:via-indigo-100 { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:via-indigo-200 { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:via-indigo-300 { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:via-indigo-400 { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:via-indigo-500 { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:via-indigo-600 { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:via-indigo-700 { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:via-indigo-800 { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:via-indigo-900 { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:via-purple-100 { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:via-purple-200 { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:via-purple-300 { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:via-purple-400 { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:via-purple-500 { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:via-purple-600 { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:via-purple-700 { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:via-purple-800 { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:via-purple-900 { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:via-pink-100 { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:via-pink-200 { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:via-pink-300 { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:via-pink-400 { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:via-pink-500 { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:via-pink-600 { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:via-pink-700 { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:via-pink-800 { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:via-pink-900 { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:to-transparent { + --gradient-to-color: transparent; + } + + .md\:to-current { + --gradient-to-color: currentColor; + } + + .md\:to-black { + --gradient-to-color: #000; + } + + .md\:to-white { + --gradient-to-color: #fff; + } + + .md\:to-gray-100 { + --gradient-to-color: #f7fafc; + } + + .md\:to-gray-200 { + --gradient-to-color: #edf2f7; + } + + .md\:to-gray-300 { + --gradient-to-color: #e2e8f0; + } + + .md\:to-gray-400 { + --gradient-to-color: #cbd5e0; + } + + .md\:to-gray-500 { + --gradient-to-color: #a0aec0; + } + + .md\:to-gray-600 { + --gradient-to-color: #718096; + } + + .md\:to-gray-700 { + --gradient-to-color: #4a5568; + } + + .md\:to-gray-800 { + --gradient-to-color: #2d3748; + } + + .md\:to-gray-900 { + --gradient-to-color: #1a202c; + } + + .md\:to-red-100 { + --gradient-to-color: #fff5f5; + } + + .md\:to-red-200 { + --gradient-to-color: #fed7d7; + } + + .md\:to-red-300 { + --gradient-to-color: #feb2b2; + } + + .md\:to-red-400 { + --gradient-to-color: #fc8181; + } + + .md\:to-red-500 { + --gradient-to-color: #f56565; + } + + .md\:to-red-600 { + --gradient-to-color: #e53e3e; + } + + .md\:to-red-700 { + --gradient-to-color: #c53030; + } + + .md\:to-red-800 { + --gradient-to-color: #9b2c2c; + } + + .md\:to-red-900 { + --gradient-to-color: #742a2a; + } + + .md\:to-orange-100 { + --gradient-to-color: #fffaf0; + } + + .md\:to-orange-200 { + --gradient-to-color: #feebc8; + } + + .md\:to-orange-300 { + --gradient-to-color: #fbd38d; + } + + .md\:to-orange-400 { + --gradient-to-color: #f6ad55; + } + + .md\:to-orange-500 { + --gradient-to-color: #ed8936; + } + + .md\:to-orange-600 { + --gradient-to-color: #dd6b20; + } + + .md\:to-orange-700 { + --gradient-to-color: #c05621; + } + + .md\:to-orange-800 { + --gradient-to-color: #9c4221; + } + + .md\:to-orange-900 { + --gradient-to-color: #7b341e; + } + + .md\:to-yellow-100 { + --gradient-to-color: #fffff0; + } + + .md\:to-yellow-200 { + --gradient-to-color: #fefcbf; + } + + .md\:to-yellow-300 { + --gradient-to-color: #faf089; + } + + .md\:to-yellow-400 { + --gradient-to-color: #f6e05e; + } + + .md\:to-yellow-500 { + --gradient-to-color: #ecc94b; + } + + .md\:to-yellow-600 { + --gradient-to-color: #d69e2e; + } + + .md\:to-yellow-700 { + --gradient-to-color: #b7791f; + } + + .md\:to-yellow-800 { + --gradient-to-color: #975a16; + } + + .md\:to-yellow-900 { + --gradient-to-color: #744210; + } + + .md\:to-green-100 { + --gradient-to-color: #f0fff4; + } + + .md\:to-green-200 { + --gradient-to-color: #c6f6d5; + } + + .md\:to-green-300 { + --gradient-to-color: #9ae6b4; + } + + .md\:to-green-400 { + --gradient-to-color: #68d391; + } + + .md\:to-green-500 { + --gradient-to-color: #48bb78; + } + + .md\:to-green-600 { + --gradient-to-color: #38a169; + } + + .md\:to-green-700 { + --gradient-to-color: #2f855a; + } + + .md\:to-green-800 { + --gradient-to-color: #276749; + } + + .md\:to-green-900 { + --gradient-to-color: #22543d; + } + + .md\:to-teal-100 { + --gradient-to-color: #e6fffa; + } + + .md\:to-teal-200 { + --gradient-to-color: #b2f5ea; + } + + .md\:to-teal-300 { + --gradient-to-color: #81e6d9; + } + + .md\:to-teal-400 { + --gradient-to-color: #4fd1c5; + } + + .md\:to-teal-500 { + --gradient-to-color: #38b2ac; + } + + .md\:to-teal-600 { + --gradient-to-color: #319795; + } + + .md\:to-teal-700 { + --gradient-to-color: #2c7a7b; + } + + .md\:to-teal-800 { + --gradient-to-color: #285e61; + } + + .md\:to-teal-900 { + --gradient-to-color: #234e52; + } + + .md\:to-blue-100 { + --gradient-to-color: #ebf8ff; + } + + .md\:to-blue-200 { + --gradient-to-color: #bee3f8; + } + + .md\:to-blue-300 { + --gradient-to-color: #90cdf4; + } + + .md\:to-blue-400 { + --gradient-to-color: #63b3ed; + } + + .md\:to-blue-500 { + --gradient-to-color: #4299e1; + } + + .md\:to-blue-600 { + --gradient-to-color: #3182ce; + } + + .md\:to-blue-700 { + --gradient-to-color: #2b6cb0; + } + + .md\:to-blue-800 { + --gradient-to-color: #2c5282; + } + + .md\:to-blue-900 { + --gradient-to-color: #2a4365; + } + + .md\:to-indigo-100 { + --gradient-to-color: #ebf4ff; + } + + .md\:to-indigo-200 { + --gradient-to-color: #c3dafe; + } + + .md\:to-indigo-300 { + --gradient-to-color: #a3bffa; + } + + .md\:to-indigo-400 { + --gradient-to-color: #7f9cf5; + } + + .md\:to-indigo-500 { + --gradient-to-color: #667eea; + } + + .md\:to-indigo-600 { + --gradient-to-color: #5a67d8; + } + + .md\:to-indigo-700 { + --gradient-to-color: #4c51bf; + } + + .md\:to-indigo-800 { + --gradient-to-color: #434190; + } + + .md\:to-indigo-900 { + --gradient-to-color: #3c366b; + } + + .md\:to-purple-100 { + --gradient-to-color: #faf5ff; + } + + .md\:to-purple-200 { + --gradient-to-color: #e9d8fd; + } + + .md\:to-purple-300 { + --gradient-to-color: #d6bcfa; + } + + .md\:to-purple-400 { + --gradient-to-color: #b794f4; + } + + .md\:to-purple-500 { + --gradient-to-color: #9f7aea; + } + + .md\:to-purple-600 { + --gradient-to-color: #805ad5; + } + + .md\:to-purple-700 { + --gradient-to-color: #6b46c1; + } + + .md\:to-purple-800 { + --gradient-to-color: #553c9a; + } + + .md\:to-purple-900 { + --gradient-to-color: #44337a; + } + + .md\:to-pink-100 { + --gradient-to-color: #fff5f7; + } + + .md\:to-pink-200 { + --gradient-to-color: #fed7e2; + } + + .md\:to-pink-300 { + --gradient-to-color: #fbb6ce; + } + + .md\:to-pink-400 { + --gradient-to-color: #f687b3; + } + + .md\:to-pink-500 { + --gradient-to-color: #ed64a6; + } + + .md\:to-pink-600 { + --gradient-to-color: #d53f8c; + } + + .md\:to-pink-700 { + --gradient-to-color: #b83280; + } + + .md\:to-pink-800 { + --gradient-to-color: #97266d; + } + + .md\:to-pink-900 { + --gradient-to-color: #702459; + } + + .md\:hover\:from-transparent:hover { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:hover\:from-current:hover { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:hover\:from-black:hover { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:hover\:from-white:hover { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:hover\:from-gray-100:hover { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:hover\:from-gray-200:hover { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:hover\:from-gray-300:hover { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:hover\:from-gray-400:hover { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:hover\:from-gray-500:hover { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:hover\:from-gray-600:hover { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:hover\:from-gray-700:hover { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:hover\:from-gray-800:hover { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:hover\:from-gray-900:hover { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:hover\:from-red-100:hover { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:hover\:from-red-200:hover { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:hover\:from-red-300:hover { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:hover\:from-red-400:hover { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:hover\:from-red-500:hover { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:hover\:from-red-600:hover { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:hover\:from-red-700:hover { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:hover\:from-red-800:hover { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:hover\:from-red-900:hover { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:hover\:from-orange-100:hover { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:hover\:from-orange-200:hover { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:hover\:from-orange-300:hover { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:hover\:from-orange-400:hover { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:hover\:from-orange-500:hover { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:hover\:from-orange-600:hover { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:hover\:from-orange-700:hover { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:hover\:from-orange-800:hover { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:hover\:from-orange-900:hover { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:hover\:from-yellow-100:hover { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:hover\:from-yellow-200:hover { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:hover\:from-yellow-300:hover { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:hover\:from-yellow-400:hover { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:hover\:from-yellow-500:hover { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:hover\:from-yellow-600:hover { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:hover\:from-yellow-700:hover { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:hover\:from-yellow-800:hover { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:hover\:from-yellow-900:hover { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:hover\:from-green-100:hover { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:hover\:from-green-200:hover { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:hover\:from-green-300:hover { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:hover\:from-green-400:hover { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:hover\:from-green-500:hover { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:hover\:from-green-600:hover { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:hover\:from-green-700:hover { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:hover\:from-green-800:hover { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:hover\:from-green-900:hover { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:hover\:from-teal-100:hover { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:hover\:from-teal-200:hover { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:hover\:from-teal-300:hover { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:hover\:from-teal-400:hover { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:hover\:from-teal-500:hover { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:hover\:from-teal-600:hover { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:hover\:from-teal-700:hover { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:hover\:from-teal-800:hover { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:hover\:from-teal-900:hover { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:hover\:from-blue-100:hover { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:hover\:from-blue-200:hover { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:hover\:from-blue-300:hover { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:hover\:from-blue-400:hover { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:hover\:from-blue-500:hover { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:hover\:from-blue-600:hover { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:hover\:from-blue-700:hover { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:hover\:from-blue-800:hover { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:hover\:from-blue-900:hover { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:hover\:from-indigo-100:hover { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:hover\:from-indigo-200:hover { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:hover\:from-indigo-300:hover { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:hover\:from-indigo-400:hover { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:hover\:from-indigo-500:hover { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:hover\:from-indigo-600:hover { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:hover\:from-indigo-700:hover { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:hover\:from-indigo-800:hover { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:hover\:from-indigo-900:hover { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:hover\:from-purple-100:hover { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:hover\:from-purple-200:hover { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:hover\:from-purple-300:hover { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:hover\:from-purple-400:hover { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:hover\:from-purple-500:hover { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:hover\:from-purple-600:hover { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:hover\:from-purple-700:hover { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:hover\:from-purple-800:hover { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:hover\:from-purple-900:hover { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:hover\:from-pink-100:hover { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:hover\:from-pink-200:hover { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:hover\:from-pink-300:hover { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:hover\:from-pink-400:hover { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:hover\:from-pink-500:hover { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:hover\:from-pink-600:hover { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:hover\:from-pink-700:hover { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:hover\:from-pink-800:hover { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:hover\:from-pink-900:hover { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:hover\:via-transparent:hover { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:hover\:via-current:hover { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:hover\:via-black:hover { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:hover\:via-white:hover { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:hover\:via-gray-100:hover { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:hover\:via-gray-200:hover { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:hover\:via-gray-300:hover { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:hover\:via-gray-400:hover { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:hover\:via-gray-500:hover { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:hover\:via-gray-600:hover { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:hover\:via-gray-700:hover { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:hover\:via-gray-800:hover { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:hover\:via-gray-900:hover { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:hover\:via-red-100:hover { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:hover\:via-red-200:hover { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:hover\:via-red-300:hover { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:hover\:via-red-400:hover { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:hover\:via-red-500:hover { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:hover\:via-red-600:hover { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:hover\:via-red-700:hover { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:hover\:via-red-800:hover { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:hover\:via-red-900:hover { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:hover\:via-orange-100:hover { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:hover\:via-orange-200:hover { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:hover\:via-orange-300:hover { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:hover\:via-orange-400:hover { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:hover\:via-orange-500:hover { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:hover\:via-orange-600:hover { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:hover\:via-orange-700:hover { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:hover\:via-orange-800:hover { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:hover\:via-orange-900:hover { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:hover\:via-yellow-100:hover { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:hover\:via-yellow-200:hover { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:hover\:via-yellow-300:hover { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:hover\:via-yellow-400:hover { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:hover\:via-yellow-500:hover { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:hover\:via-yellow-600:hover { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:hover\:via-yellow-700:hover { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:hover\:via-yellow-800:hover { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:hover\:via-yellow-900:hover { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:hover\:via-green-100:hover { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:hover\:via-green-200:hover { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:hover\:via-green-300:hover { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:hover\:via-green-400:hover { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:hover\:via-green-500:hover { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:hover\:via-green-600:hover { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:hover\:via-green-700:hover { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:hover\:via-green-800:hover { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:hover\:via-green-900:hover { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:hover\:via-teal-100:hover { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:hover\:via-teal-200:hover { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:hover\:via-teal-300:hover { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:hover\:via-teal-400:hover { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:hover\:via-teal-500:hover { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:hover\:via-teal-600:hover { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:hover\:via-teal-700:hover { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:hover\:via-teal-800:hover { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:hover\:via-teal-900:hover { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:hover\:via-blue-100:hover { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:hover\:via-blue-200:hover { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:hover\:via-blue-300:hover { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:hover\:via-blue-400:hover { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:hover\:via-blue-500:hover { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:hover\:via-blue-600:hover { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:hover\:via-blue-700:hover { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:hover\:via-blue-800:hover { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:hover\:via-blue-900:hover { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:hover\:via-indigo-100:hover { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:hover\:via-indigo-200:hover { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:hover\:via-indigo-300:hover { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:hover\:via-indigo-400:hover { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:hover\:via-indigo-500:hover { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:hover\:via-indigo-600:hover { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:hover\:via-indigo-700:hover { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:hover\:via-indigo-800:hover { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:hover\:via-indigo-900:hover { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:hover\:via-purple-100:hover { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:hover\:via-purple-200:hover { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:hover\:via-purple-300:hover { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:hover\:via-purple-400:hover { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:hover\:via-purple-500:hover { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:hover\:via-purple-600:hover { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:hover\:via-purple-700:hover { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:hover\:via-purple-800:hover { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:hover\:via-purple-900:hover { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:hover\:via-pink-100:hover { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:hover\:via-pink-200:hover { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:hover\:via-pink-300:hover { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:hover\:via-pink-400:hover { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:hover\:via-pink-500:hover { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:hover\:via-pink-600:hover { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:hover\:via-pink-700:hover { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:hover\:via-pink-800:hover { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:hover\:via-pink-900:hover { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:hover\:to-transparent:hover { + --gradient-to-color: transparent; + } + + .md\:hover\:to-current:hover { + --gradient-to-color: currentColor; + } + + .md\:hover\:to-black:hover { + --gradient-to-color: #000; + } + + .md\:hover\:to-white:hover { + --gradient-to-color: #fff; + } + + .md\:hover\:to-gray-100:hover { + --gradient-to-color: #f7fafc; + } + + .md\:hover\:to-gray-200:hover { + --gradient-to-color: #edf2f7; + } + + .md\:hover\:to-gray-300:hover { + --gradient-to-color: #e2e8f0; + } + + .md\:hover\:to-gray-400:hover { + --gradient-to-color: #cbd5e0; + } + + .md\:hover\:to-gray-500:hover { + --gradient-to-color: #a0aec0; + } + + .md\:hover\:to-gray-600:hover { + --gradient-to-color: #718096; + } + + .md\:hover\:to-gray-700:hover { + --gradient-to-color: #4a5568; + } + + .md\:hover\:to-gray-800:hover { + --gradient-to-color: #2d3748; + } + + .md\:hover\:to-gray-900:hover { + --gradient-to-color: #1a202c; + } + + .md\:hover\:to-red-100:hover { + --gradient-to-color: #fff5f5; + } + + .md\:hover\:to-red-200:hover { + --gradient-to-color: #fed7d7; + } + + .md\:hover\:to-red-300:hover { + --gradient-to-color: #feb2b2; + } + + .md\:hover\:to-red-400:hover { + --gradient-to-color: #fc8181; + } + + .md\:hover\:to-red-500:hover { + --gradient-to-color: #f56565; + } + + .md\:hover\:to-red-600:hover { + --gradient-to-color: #e53e3e; + } + + .md\:hover\:to-red-700:hover { + --gradient-to-color: #c53030; + } + + .md\:hover\:to-red-800:hover { + --gradient-to-color: #9b2c2c; + } + + .md\:hover\:to-red-900:hover { + --gradient-to-color: #742a2a; + } + + .md\:hover\:to-orange-100:hover { + --gradient-to-color: #fffaf0; + } + + .md\:hover\:to-orange-200:hover { + --gradient-to-color: #feebc8; + } + + .md\:hover\:to-orange-300:hover { + --gradient-to-color: #fbd38d; + } + + .md\:hover\:to-orange-400:hover { + --gradient-to-color: #f6ad55; + } + + .md\:hover\:to-orange-500:hover { + --gradient-to-color: #ed8936; + } + + .md\:hover\:to-orange-600:hover { + --gradient-to-color: #dd6b20; + } + + .md\:hover\:to-orange-700:hover { + --gradient-to-color: #c05621; + } + + .md\:hover\:to-orange-800:hover { + --gradient-to-color: #9c4221; + } + + .md\:hover\:to-orange-900:hover { + --gradient-to-color: #7b341e; + } + + .md\:hover\:to-yellow-100:hover { + --gradient-to-color: #fffff0; + } + + .md\:hover\:to-yellow-200:hover { + --gradient-to-color: #fefcbf; + } + + .md\:hover\:to-yellow-300:hover { + --gradient-to-color: #faf089; + } + + .md\:hover\:to-yellow-400:hover { + --gradient-to-color: #f6e05e; + } + + .md\:hover\:to-yellow-500:hover { + --gradient-to-color: #ecc94b; + } + + .md\:hover\:to-yellow-600:hover { + --gradient-to-color: #d69e2e; + } + + .md\:hover\:to-yellow-700:hover { + --gradient-to-color: #b7791f; + } + + .md\:hover\:to-yellow-800:hover { + --gradient-to-color: #975a16; + } + + .md\:hover\:to-yellow-900:hover { + --gradient-to-color: #744210; + } + + .md\:hover\:to-green-100:hover { + --gradient-to-color: #f0fff4; + } + + .md\:hover\:to-green-200:hover { + --gradient-to-color: #c6f6d5; + } + + .md\:hover\:to-green-300:hover { + --gradient-to-color: #9ae6b4; + } + + .md\:hover\:to-green-400:hover { + --gradient-to-color: #68d391; + } + + .md\:hover\:to-green-500:hover { + --gradient-to-color: #48bb78; + } + + .md\:hover\:to-green-600:hover { + --gradient-to-color: #38a169; + } + + .md\:hover\:to-green-700:hover { + --gradient-to-color: #2f855a; + } + + .md\:hover\:to-green-800:hover { + --gradient-to-color: #276749; + } + + .md\:hover\:to-green-900:hover { + --gradient-to-color: #22543d; + } + + .md\:hover\:to-teal-100:hover { + --gradient-to-color: #e6fffa; + } + + .md\:hover\:to-teal-200:hover { + --gradient-to-color: #b2f5ea; + } + + .md\:hover\:to-teal-300:hover { + --gradient-to-color: #81e6d9; + } + + .md\:hover\:to-teal-400:hover { + --gradient-to-color: #4fd1c5; + } + + .md\:hover\:to-teal-500:hover { + --gradient-to-color: #38b2ac; + } + + .md\:hover\:to-teal-600:hover { + --gradient-to-color: #319795; + } + + .md\:hover\:to-teal-700:hover { + --gradient-to-color: #2c7a7b; + } + + .md\:hover\:to-teal-800:hover { + --gradient-to-color: #285e61; + } + + .md\:hover\:to-teal-900:hover { + --gradient-to-color: #234e52; + } + + .md\:hover\:to-blue-100:hover { + --gradient-to-color: #ebf8ff; + } + + .md\:hover\:to-blue-200:hover { + --gradient-to-color: #bee3f8; + } + + .md\:hover\:to-blue-300:hover { + --gradient-to-color: #90cdf4; + } + + .md\:hover\:to-blue-400:hover { + --gradient-to-color: #63b3ed; + } + + .md\:hover\:to-blue-500:hover { + --gradient-to-color: #4299e1; + } + + .md\:hover\:to-blue-600:hover { + --gradient-to-color: #3182ce; + } + + .md\:hover\:to-blue-700:hover { + --gradient-to-color: #2b6cb0; + } + + .md\:hover\:to-blue-800:hover { + --gradient-to-color: #2c5282; + } + + .md\:hover\:to-blue-900:hover { + --gradient-to-color: #2a4365; + } + + .md\:hover\:to-indigo-100:hover { + --gradient-to-color: #ebf4ff; + } + + .md\:hover\:to-indigo-200:hover { + --gradient-to-color: #c3dafe; + } + + .md\:hover\:to-indigo-300:hover { + --gradient-to-color: #a3bffa; + } + + .md\:hover\:to-indigo-400:hover { + --gradient-to-color: #7f9cf5; + } + + .md\:hover\:to-indigo-500:hover { + --gradient-to-color: #667eea; + } + + .md\:hover\:to-indigo-600:hover { + --gradient-to-color: #5a67d8; + } + + .md\:hover\:to-indigo-700:hover { + --gradient-to-color: #4c51bf; + } + + .md\:hover\:to-indigo-800:hover { + --gradient-to-color: #434190; + } + + .md\:hover\:to-indigo-900:hover { + --gradient-to-color: #3c366b; + } + + .md\:hover\:to-purple-100:hover { + --gradient-to-color: #faf5ff; + } + + .md\:hover\:to-purple-200:hover { + --gradient-to-color: #e9d8fd; + } + + .md\:hover\:to-purple-300:hover { + --gradient-to-color: #d6bcfa; + } + + .md\:hover\:to-purple-400:hover { + --gradient-to-color: #b794f4; + } + + .md\:hover\:to-purple-500:hover { + --gradient-to-color: #9f7aea; + } + + .md\:hover\:to-purple-600:hover { + --gradient-to-color: #805ad5; + } + + .md\:hover\:to-purple-700:hover { + --gradient-to-color: #6b46c1; + } + + .md\:hover\:to-purple-800:hover { + --gradient-to-color: #553c9a; + } + + .md\:hover\:to-purple-900:hover { + --gradient-to-color: #44337a; + } + + .md\:hover\:to-pink-100:hover { + --gradient-to-color: #fff5f7; + } + + .md\:hover\:to-pink-200:hover { + --gradient-to-color: #fed7e2; + } + + .md\:hover\:to-pink-300:hover { + --gradient-to-color: #fbb6ce; + } + + .md\:hover\:to-pink-400:hover { + --gradient-to-color: #f687b3; + } + + .md\:hover\:to-pink-500:hover { + --gradient-to-color: #ed64a6; + } + + .md\:hover\:to-pink-600:hover { + --gradient-to-color: #d53f8c; + } + + .md\:hover\:to-pink-700:hover { + --gradient-to-color: #b83280; + } + + .md\:hover\:to-pink-800:hover { + --gradient-to-color: #97266d; + } + + .md\:hover\:to-pink-900:hover { + --gradient-to-color: #702459; + } + + .md\:focus\:from-transparent:focus { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:focus\:from-current:focus { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:focus\:from-black:focus { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:focus\:from-white:focus { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:focus\:from-gray-100:focus { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:focus\:from-gray-200:focus { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:focus\:from-gray-300:focus { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:focus\:from-gray-400:focus { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:focus\:from-gray-500:focus { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:focus\:from-gray-600:focus { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:focus\:from-gray-700:focus { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:focus\:from-gray-800:focus { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:focus\:from-gray-900:focus { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:focus\:from-red-100:focus { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:focus\:from-red-200:focus { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:focus\:from-red-300:focus { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:focus\:from-red-400:focus { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:focus\:from-red-500:focus { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:focus\:from-red-600:focus { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:focus\:from-red-700:focus { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:focus\:from-red-800:focus { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:focus\:from-red-900:focus { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:focus\:from-orange-100:focus { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:focus\:from-orange-200:focus { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:focus\:from-orange-300:focus { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:focus\:from-orange-400:focus { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:focus\:from-orange-500:focus { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:focus\:from-orange-600:focus { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:focus\:from-orange-700:focus { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:focus\:from-orange-800:focus { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:focus\:from-orange-900:focus { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:focus\:from-yellow-100:focus { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:focus\:from-yellow-200:focus { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:focus\:from-yellow-300:focus { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:focus\:from-yellow-400:focus { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:focus\:from-yellow-500:focus { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:focus\:from-yellow-600:focus { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:focus\:from-yellow-700:focus { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:focus\:from-yellow-800:focus { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:focus\:from-yellow-900:focus { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:focus\:from-green-100:focus { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:focus\:from-green-200:focus { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:focus\:from-green-300:focus { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:focus\:from-green-400:focus { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:focus\:from-green-500:focus { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:focus\:from-green-600:focus { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:focus\:from-green-700:focus { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:focus\:from-green-800:focus { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:focus\:from-green-900:focus { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:focus\:from-teal-100:focus { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:focus\:from-teal-200:focus { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:focus\:from-teal-300:focus { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:focus\:from-teal-400:focus { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:focus\:from-teal-500:focus { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:focus\:from-teal-600:focus { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:focus\:from-teal-700:focus { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:focus\:from-teal-800:focus { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:focus\:from-teal-900:focus { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:focus\:from-blue-100:focus { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:focus\:from-blue-200:focus { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:focus\:from-blue-300:focus { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:focus\:from-blue-400:focus { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:focus\:from-blue-500:focus { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:focus\:from-blue-600:focus { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:focus\:from-blue-700:focus { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:focus\:from-blue-800:focus { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:focus\:from-blue-900:focus { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:focus\:from-indigo-100:focus { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:focus\:from-indigo-200:focus { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:focus\:from-indigo-300:focus { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:focus\:from-indigo-400:focus { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:focus\:from-indigo-500:focus { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:focus\:from-indigo-600:focus { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:focus\:from-indigo-700:focus { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:focus\:from-indigo-800:focus { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:focus\:from-indigo-900:focus { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:focus\:from-purple-100:focus { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:focus\:from-purple-200:focus { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:focus\:from-purple-300:focus { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:focus\:from-purple-400:focus { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:focus\:from-purple-500:focus { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:focus\:from-purple-600:focus { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:focus\:from-purple-700:focus { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:focus\:from-purple-800:focus { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:focus\:from-purple-900:focus { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:focus\:from-pink-100:focus { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:focus\:from-pink-200:focus { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:focus\:from-pink-300:focus { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:focus\:from-pink-400:focus { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:focus\:from-pink-500:focus { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:focus\:from-pink-600:focus { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:focus\:from-pink-700:focus { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:focus\:from-pink-800:focus { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:focus\:from-pink-900:focus { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:focus\:via-transparent:focus { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:focus\:via-current:focus { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:focus\:via-black:focus { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .md\:focus\:via-white:focus { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .md\:focus\:via-gray-100:focus { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .md\:focus\:via-gray-200:focus { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .md\:focus\:via-gray-300:focus { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .md\:focus\:via-gray-400:focus { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .md\:focus\:via-gray-500:focus { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .md\:focus\:via-gray-600:focus { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .md\:focus\:via-gray-700:focus { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .md\:focus\:via-gray-800:focus { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .md\:focus\:via-gray-900:focus { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .md\:focus\:via-red-100:focus { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .md\:focus\:via-red-200:focus { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .md\:focus\:via-red-300:focus { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .md\:focus\:via-red-400:focus { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .md\:focus\:via-red-500:focus { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .md\:focus\:via-red-600:focus { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .md\:focus\:via-red-700:focus { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .md\:focus\:via-red-800:focus { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .md\:focus\:via-red-900:focus { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .md\:focus\:via-orange-100:focus { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .md\:focus\:via-orange-200:focus { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .md\:focus\:via-orange-300:focus { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .md\:focus\:via-orange-400:focus { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .md\:focus\:via-orange-500:focus { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .md\:focus\:via-orange-600:focus { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .md\:focus\:via-orange-700:focus { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .md\:focus\:via-orange-800:focus { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .md\:focus\:via-orange-900:focus { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .md\:focus\:via-yellow-100:focus { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .md\:focus\:via-yellow-200:focus { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .md\:focus\:via-yellow-300:focus { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .md\:focus\:via-yellow-400:focus { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .md\:focus\:via-yellow-500:focus { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .md\:focus\:via-yellow-600:focus { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .md\:focus\:via-yellow-700:focus { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .md\:focus\:via-yellow-800:focus { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .md\:focus\:via-yellow-900:focus { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .md\:focus\:via-green-100:focus { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .md\:focus\:via-green-200:focus { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .md\:focus\:via-green-300:focus { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .md\:focus\:via-green-400:focus { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .md\:focus\:via-green-500:focus { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .md\:focus\:via-green-600:focus { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .md\:focus\:via-green-700:focus { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .md\:focus\:via-green-800:focus { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .md\:focus\:via-green-900:focus { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .md\:focus\:via-teal-100:focus { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .md\:focus\:via-teal-200:focus { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .md\:focus\:via-teal-300:focus { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .md\:focus\:via-teal-400:focus { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .md\:focus\:via-teal-500:focus { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .md\:focus\:via-teal-600:focus { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .md\:focus\:via-teal-700:focus { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .md\:focus\:via-teal-800:focus { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .md\:focus\:via-teal-900:focus { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .md\:focus\:via-blue-100:focus { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .md\:focus\:via-blue-200:focus { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .md\:focus\:via-blue-300:focus { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .md\:focus\:via-blue-400:focus { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .md\:focus\:via-blue-500:focus { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .md\:focus\:via-blue-600:focus { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .md\:focus\:via-blue-700:focus { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .md\:focus\:via-blue-800:focus { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .md\:focus\:via-blue-900:focus { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .md\:focus\:via-indigo-100:focus { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .md\:focus\:via-indigo-200:focus { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .md\:focus\:via-indigo-300:focus { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .md\:focus\:via-indigo-400:focus { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .md\:focus\:via-indigo-500:focus { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .md\:focus\:via-indigo-600:focus { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .md\:focus\:via-indigo-700:focus { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .md\:focus\:via-indigo-800:focus { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .md\:focus\:via-indigo-900:focus { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .md\:focus\:via-purple-100:focus { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .md\:focus\:via-purple-200:focus { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .md\:focus\:via-purple-300:focus { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .md\:focus\:via-purple-400:focus { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .md\:focus\:via-purple-500:focus { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .md\:focus\:via-purple-600:focus { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .md\:focus\:via-purple-700:focus { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .md\:focus\:via-purple-800:focus { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .md\:focus\:via-purple-900:focus { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .md\:focus\:via-pink-100:focus { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .md\:focus\:via-pink-200:focus { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .md\:focus\:via-pink-300:focus { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .md\:focus\:via-pink-400:focus { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .md\:focus\:via-pink-500:focus { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .md\:focus\:via-pink-600:focus { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .md\:focus\:via-pink-700:focus { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .md\:focus\:via-pink-800:focus { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .md\:focus\:via-pink-900:focus { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .md\:focus\:to-transparent:focus { + --gradient-to-color: transparent; + } + + .md\:focus\:to-current:focus { + --gradient-to-color: currentColor; + } + + .md\:focus\:to-black:focus { + --gradient-to-color: #000; + } + + .md\:focus\:to-white:focus { + --gradient-to-color: #fff; + } + + .md\:focus\:to-gray-100:focus { + --gradient-to-color: #f7fafc; + } + + .md\:focus\:to-gray-200:focus { + --gradient-to-color: #edf2f7; + } + + .md\:focus\:to-gray-300:focus { + --gradient-to-color: #e2e8f0; + } + + .md\:focus\:to-gray-400:focus { + --gradient-to-color: #cbd5e0; + } + + .md\:focus\:to-gray-500:focus { + --gradient-to-color: #a0aec0; + } + + .md\:focus\:to-gray-600:focus { + --gradient-to-color: #718096; + } + + .md\:focus\:to-gray-700:focus { + --gradient-to-color: #4a5568; + } + + .md\:focus\:to-gray-800:focus { + --gradient-to-color: #2d3748; + } + + .md\:focus\:to-gray-900:focus { + --gradient-to-color: #1a202c; + } + + .md\:focus\:to-red-100:focus { + --gradient-to-color: #fff5f5; + } + + .md\:focus\:to-red-200:focus { + --gradient-to-color: #fed7d7; + } + + .md\:focus\:to-red-300:focus { + --gradient-to-color: #feb2b2; + } + + .md\:focus\:to-red-400:focus { + --gradient-to-color: #fc8181; + } + + .md\:focus\:to-red-500:focus { + --gradient-to-color: #f56565; + } + + .md\:focus\:to-red-600:focus { + --gradient-to-color: #e53e3e; + } + + .md\:focus\:to-red-700:focus { + --gradient-to-color: #c53030; + } + + .md\:focus\:to-red-800:focus { + --gradient-to-color: #9b2c2c; + } + + .md\:focus\:to-red-900:focus { + --gradient-to-color: #742a2a; + } + + .md\:focus\:to-orange-100:focus { + --gradient-to-color: #fffaf0; + } + + .md\:focus\:to-orange-200:focus { + --gradient-to-color: #feebc8; + } + + .md\:focus\:to-orange-300:focus { + --gradient-to-color: #fbd38d; + } + + .md\:focus\:to-orange-400:focus { + --gradient-to-color: #f6ad55; + } + + .md\:focus\:to-orange-500:focus { + --gradient-to-color: #ed8936; + } + + .md\:focus\:to-orange-600:focus { + --gradient-to-color: #dd6b20; + } + + .md\:focus\:to-orange-700:focus { + --gradient-to-color: #c05621; + } + + .md\:focus\:to-orange-800:focus { + --gradient-to-color: #9c4221; + } + + .md\:focus\:to-orange-900:focus { + --gradient-to-color: #7b341e; + } + + .md\:focus\:to-yellow-100:focus { + --gradient-to-color: #fffff0; + } + + .md\:focus\:to-yellow-200:focus { + --gradient-to-color: #fefcbf; + } + + .md\:focus\:to-yellow-300:focus { + --gradient-to-color: #faf089; + } + + .md\:focus\:to-yellow-400:focus { + --gradient-to-color: #f6e05e; + } + + .md\:focus\:to-yellow-500:focus { + --gradient-to-color: #ecc94b; + } + + .md\:focus\:to-yellow-600:focus { + --gradient-to-color: #d69e2e; + } + + .md\:focus\:to-yellow-700:focus { + --gradient-to-color: #b7791f; + } + + .md\:focus\:to-yellow-800:focus { + --gradient-to-color: #975a16; + } + + .md\:focus\:to-yellow-900:focus { + --gradient-to-color: #744210; + } + + .md\:focus\:to-green-100:focus { + --gradient-to-color: #f0fff4; + } + + .md\:focus\:to-green-200:focus { + --gradient-to-color: #c6f6d5; + } + + .md\:focus\:to-green-300:focus { + --gradient-to-color: #9ae6b4; + } + + .md\:focus\:to-green-400:focus { + --gradient-to-color: #68d391; + } + + .md\:focus\:to-green-500:focus { + --gradient-to-color: #48bb78; + } + + .md\:focus\:to-green-600:focus { + --gradient-to-color: #38a169; + } + + .md\:focus\:to-green-700:focus { + --gradient-to-color: #2f855a; + } + + .md\:focus\:to-green-800:focus { + --gradient-to-color: #276749; + } + + .md\:focus\:to-green-900:focus { + --gradient-to-color: #22543d; + } + + .md\:focus\:to-teal-100:focus { + --gradient-to-color: #e6fffa; + } + + .md\:focus\:to-teal-200:focus { + --gradient-to-color: #b2f5ea; + } + + .md\:focus\:to-teal-300:focus { + --gradient-to-color: #81e6d9; + } + + .md\:focus\:to-teal-400:focus { + --gradient-to-color: #4fd1c5; + } + + .md\:focus\:to-teal-500:focus { + --gradient-to-color: #38b2ac; + } + + .md\:focus\:to-teal-600:focus { + --gradient-to-color: #319795; + } + + .md\:focus\:to-teal-700:focus { + --gradient-to-color: #2c7a7b; + } + + .md\:focus\:to-teal-800:focus { + --gradient-to-color: #285e61; + } + + .md\:focus\:to-teal-900:focus { + --gradient-to-color: #234e52; + } + + .md\:focus\:to-blue-100:focus { + --gradient-to-color: #ebf8ff; + } + + .md\:focus\:to-blue-200:focus { + --gradient-to-color: #bee3f8; + } + + .md\:focus\:to-blue-300:focus { + --gradient-to-color: #90cdf4; + } + + .md\:focus\:to-blue-400:focus { + --gradient-to-color: #63b3ed; + } + + .md\:focus\:to-blue-500:focus { + --gradient-to-color: #4299e1; + } + + .md\:focus\:to-blue-600:focus { + --gradient-to-color: #3182ce; + } + + .md\:focus\:to-blue-700:focus { + --gradient-to-color: #2b6cb0; + } + + .md\:focus\:to-blue-800:focus { + --gradient-to-color: #2c5282; + } + + .md\:focus\:to-blue-900:focus { + --gradient-to-color: #2a4365; + } + + .md\:focus\:to-indigo-100:focus { + --gradient-to-color: #ebf4ff; + } + + .md\:focus\:to-indigo-200:focus { + --gradient-to-color: #c3dafe; + } + + .md\:focus\:to-indigo-300:focus { + --gradient-to-color: #a3bffa; + } + + .md\:focus\:to-indigo-400:focus { + --gradient-to-color: #7f9cf5; + } + + .md\:focus\:to-indigo-500:focus { + --gradient-to-color: #667eea; + } + + .md\:focus\:to-indigo-600:focus { + --gradient-to-color: #5a67d8; + } + + .md\:focus\:to-indigo-700:focus { + --gradient-to-color: #4c51bf; + } + + .md\:focus\:to-indigo-800:focus { + --gradient-to-color: #434190; + } + + .md\:focus\:to-indigo-900:focus { + --gradient-to-color: #3c366b; + } + + .md\:focus\:to-purple-100:focus { + --gradient-to-color: #faf5ff; + } + + .md\:focus\:to-purple-200:focus { + --gradient-to-color: #e9d8fd; + } + + .md\:focus\:to-purple-300:focus { + --gradient-to-color: #d6bcfa; + } + + .md\:focus\:to-purple-400:focus { + --gradient-to-color: #b794f4; + } + + .md\:focus\:to-purple-500:focus { + --gradient-to-color: #9f7aea; + } + + .md\:focus\:to-purple-600:focus { + --gradient-to-color: #805ad5; + } + + .md\:focus\:to-purple-700:focus { + --gradient-to-color: #6b46c1; + } + + .md\:focus\:to-purple-800:focus { + --gradient-to-color: #553c9a; + } + + .md\:focus\:to-purple-900:focus { + --gradient-to-color: #44337a; + } + + .md\:focus\:to-pink-100:focus { + --gradient-to-color: #fff5f7; + } + + .md\:focus\:to-pink-200:focus { + --gradient-to-color: #fed7e2; + } + + .md\:focus\:to-pink-300:focus { + --gradient-to-color: #fbb6ce; + } + + .md\:focus\:to-pink-400:focus { + --gradient-to-color: #f687b3; + } + + .md\:focus\:to-pink-500:focus { + --gradient-to-color: #ed64a6; + } + + .md\:focus\:to-pink-600:focus { + --gradient-to-color: #d53f8c; + } + + .md\:focus\:to-pink-700:focus { + --gradient-to-color: #b83280; + } + + .md\:focus\:to-pink-800:focus { + --gradient-to-color: #97266d; + } + + .md\:focus\:to-pink-900:focus { + --gradient-to-color: #702459; + } + + .md\:bg-opacity-0 { + --bg-opacity: 0; + } + + .md\:bg-opacity-25 { + --bg-opacity: 0.25; + } + + .md\:bg-opacity-50 { + --bg-opacity: 0.5; + } + + .md\:bg-opacity-75 { + --bg-opacity: 0.75; + } + + .md\:bg-opacity-100 { + --bg-opacity: 1; + } + + .md\:hover\:bg-opacity-0:hover { + --bg-opacity: 0; + } + + .md\:hover\:bg-opacity-25:hover { + --bg-opacity: 0.25; + } + + .md\:hover\:bg-opacity-50:hover { + --bg-opacity: 0.5; + } + + .md\:hover\:bg-opacity-75:hover { + --bg-opacity: 0.75; + } + + .md\:hover\:bg-opacity-100:hover { + --bg-opacity: 1; + } + + .md\:focus\:bg-opacity-0:focus { + --bg-opacity: 0; + } + + .md\:focus\:bg-opacity-25:focus { + --bg-opacity: 0.25; + } + + .md\:focus\:bg-opacity-50:focus { + --bg-opacity: 0.5; + } + + .md\:focus\:bg-opacity-75:focus { + --bg-opacity: 0.75; + } + + .md\:focus\:bg-opacity-100:focus { + --bg-opacity: 1; + } + + .md\:bg-bottom { + background-position: bottom; + } + + .md\:bg-center { + background-position: center; + } + + .md\:bg-left { + background-position: left; + } + + .md\:bg-left-bottom { + background-position: left bottom; + } + + .md\:bg-left-top { + background-position: left top; + } + + .md\:bg-right { + background-position: right; + } + + .md\:bg-right-bottom { + background-position: right bottom; + } + + .md\:bg-right-top { + background-position: right top; + } + + .md\:bg-top { + background-position: top; + } + + .md\:bg-repeat { + background-repeat: repeat; + } + + .md\:bg-no-repeat { + background-repeat: no-repeat; + } + + .md\:bg-repeat-x { + background-repeat: repeat-x; + } + + .md\:bg-repeat-y { + background-repeat: repeat-y; + } + + .md\:bg-repeat-round { + background-repeat: round; + } + + .md\:bg-repeat-space { + background-repeat: space; + } + + .md\:bg-auto { + background-size: auto; + } + + .md\:bg-cover { + background-size: cover; + } + + .md\:bg-contain { + background-size: contain; + } + + .md\:border-collapse { + border-collapse: collapse; + } + + .md\:border-separate { + border-collapse: separate; + } + + .md\:border-transparent { + border-color: transparent; + } + + .md\:border-current { + border-color: currentColor; + } + + .md\:border-black { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .md\:border-white { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .md\:border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .md\:border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .md\:border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .md\:border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .md\:border-gray-500 { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .md\:border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .md\:border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .md\:border-gray-800 { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .md\:border-gray-900 { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .md\:border-red-100 { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .md\:border-red-200 { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .md\:border-red-300 { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .md\:border-red-400 { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .md\:border-red-500 { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .md\:border-red-600 { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .md\:border-red-700 { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .md\:border-red-800 { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .md\:border-red-900 { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .md\:border-orange-100 { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .md\:border-orange-200 { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .md\:border-orange-300 { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .md\:border-orange-400 { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .md\:border-orange-500 { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .md\:border-orange-600 { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .md\:border-orange-700 { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .md\:border-orange-800 { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .md\:border-orange-900 { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .md\:border-yellow-100 { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .md\:border-yellow-200 { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .md\:border-yellow-300 { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .md\:border-yellow-400 { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .md\:border-yellow-500 { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .md\:border-yellow-600 { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .md\:border-yellow-700 { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .md\:border-yellow-800 { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .md\:border-yellow-900 { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .md\:border-green-100 { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .md\:border-green-200 { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .md\:border-green-300 { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .md\:border-green-400 { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .md\:border-green-500 { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .md\:border-green-600 { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .md\:border-green-700 { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .md\:border-green-800 { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .md\:border-green-900 { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .md\:border-teal-100 { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .md\:border-teal-200 { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .md\:border-teal-300 { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .md\:border-teal-400 { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .md\:border-teal-500 { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .md\:border-teal-600 { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .md\:border-teal-700 { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .md\:border-teal-800 { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .md\:border-teal-900 { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .md\:border-blue-100 { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .md\:border-blue-200 { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .md\:border-blue-300 { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .md\:border-blue-400 { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .md\:border-blue-500 { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .md\:border-blue-600 { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .md\:border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .md\:border-blue-800 { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .md\:border-blue-900 { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .md\:border-indigo-100 { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .md\:border-indigo-200 { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .md\:border-indigo-300 { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .md\:border-indigo-400 { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .md\:border-indigo-500 { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .md\:border-indigo-600 { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .md\:border-indigo-700 { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .md\:border-indigo-800 { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .md\:border-indigo-900 { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .md\:border-purple-100 { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .md\:border-purple-200 { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .md\:border-purple-300 { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .md\:border-purple-400 { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .md\:border-purple-500 { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .md\:border-purple-600 { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .md\:border-purple-700 { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .md\:border-purple-800 { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .md\:border-purple-900 { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .md\:border-pink-100 { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .md\:border-pink-200 { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .md\:border-pink-300 { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .md\:border-pink-400 { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .md\:border-pink-500 { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .md\:border-pink-600 { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .md\:border-pink-700 { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .md\:border-pink-800 { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .md\:border-pink-900 { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .md\:hover\:border-transparent:hover { + border-color: transparent; + } + + .md\:hover\:border-current:hover { + border-color: currentColor; + } + + .md\:hover\:border-black:hover { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .md\:hover\:border-white:hover { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .md\:hover\:border-gray-100:hover { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .md\:hover\:border-gray-200:hover { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .md\:hover\:border-gray-300:hover { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .md\:hover\:border-gray-400:hover { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .md\:hover\:border-gray-500:hover { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .md\:hover\:border-gray-600:hover { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .md\:hover\:border-gray-700:hover { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .md\:hover\:border-gray-800:hover { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .md\:hover\:border-gray-900:hover { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .md\:hover\:border-red-100:hover { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .md\:hover\:border-red-200:hover { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .md\:hover\:border-red-300:hover { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .md\:hover\:border-red-400:hover { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .md\:hover\:border-red-500:hover { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .md\:hover\:border-red-600:hover { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .md\:hover\:border-red-700:hover { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .md\:hover\:border-red-800:hover { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .md\:hover\:border-red-900:hover { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .md\:hover\:border-orange-100:hover { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .md\:hover\:border-orange-200:hover { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .md\:hover\:border-orange-300:hover { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .md\:hover\:border-orange-400:hover { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .md\:hover\:border-orange-500:hover { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .md\:hover\:border-orange-600:hover { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .md\:hover\:border-orange-700:hover { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .md\:hover\:border-orange-800:hover { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .md\:hover\:border-orange-900:hover { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .md\:hover\:border-yellow-100:hover { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .md\:hover\:border-yellow-200:hover { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .md\:hover\:border-yellow-300:hover { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .md\:hover\:border-yellow-400:hover { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .md\:hover\:border-yellow-500:hover { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .md\:hover\:border-yellow-600:hover { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .md\:hover\:border-yellow-700:hover { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .md\:hover\:border-yellow-800:hover { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .md\:hover\:border-yellow-900:hover { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .md\:hover\:border-green-100:hover { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .md\:hover\:border-green-200:hover { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .md\:hover\:border-green-300:hover { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .md\:hover\:border-green-400:hover { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .md\:hover\:border-green-500:hover { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .md\:hover\:border-green-600:hover { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .md\:hover\:border-green-700:hover { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .md\:hover\:border-green-800:hover { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .md\:hover\:border-green-900:hover { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .md\:hover\:border-teal-100:hover { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .md\:hover\:border-teal-200:hover { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .md\:hover\:border-teal-300:hover { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .md\:hover\:border-teal-400:hover { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .md\:hover\:border-teal-500:hover { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .md\:hover\:border-teal-600:hover { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .md\:hover\:border-teal-700:hover { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .md\:hover\:border-teal-800:hover { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .md\:hover\:border-teal-900:hover { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .md\:hover\:border-blue-100:hover { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .md\:hover\:border-blue-200:hover { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .md\:hover\:border-blue-300:hover { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .md\:hover\:border-blue-400:hover { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .md\:hover\:border-blue-500:hover { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .md\:hover\:border-blue-600:hover { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .md\:hover\:border-blue-700:hover { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .md\:hover\:border-blue-800:hover { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .md\:hover\:border-blue-900:hover { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .md\:hover\:border-indigo-100:hover { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .md\:hover\:border-indigo-200:hover { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .md\:hover\:border-indigo-300:hover { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .md\:hover\:border-indigo-400:hover { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .md\:hover\:border-indigo-500:hover { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .md\:hover\:border-indigo-600:hover { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .md\:hover\:border-indigo-700:hover { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .md\:hover\:border-indigo-800:hover { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .md\:hover\:border-indigo-900:hover { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .md\:hover\:border-purple-100:hover { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .md\:hover\:border-purple-200:hover { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .md\:hover\:border-purple-300:hover { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .md\:hover\:border-purple-400:hover { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .md\:hover\:border-purple-500:hover { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .md\:hover\:border-purple-600:hover { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .md\:hover\:border-purple-700:hover { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .md\:hover\:border-purple-800:hover { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .md\:hover\:border-purple-900:hover { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .md\:hover\:border-pink-100:hover { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .md\:hover\:border-pink-200:hover { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .md\:hover\:border-pink-300:hover { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .md\:hover\:border-pink-400:hover { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .md\:hover\:border-pink-500:hover { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .md\:hover\:border-pink-600:hover { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .md\:hover\:border-pink-700:hover { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .md\:hover\:border-pink-800:hover { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .md\:hover\:border-pink-900:hover { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .md\:focus\:border-transparent:focus { + border-color: transparent; + } + + .md\:focus\:border-current:focus { + border-color: currentColor; + } + + .md\:focus\:border-black:focus { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .md\:focus\:border-white:focus { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .md\:focus\:border-gray-100:focus { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .md\:focus\:border-gray-200:focus { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .md\:focus\:border-gray-300:focus { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .md\:focus\:border-gray-400:focus { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .md\:focus\:border-gray-500:focus { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .md\:focus\:border-gray-600:focus { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .md\:focus\:border-gray-700:focus { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .md\:focus\:border-gray-800:focus { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .md\:focus\:border-gray-900:focus { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .md\:focus\:border-red-100:focus { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .md\:focus\:border-red-200:focus { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .md\:focus\:border-red-300:focus { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .md\:focus\:border-red-400:focus { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .md\:focus\:border-red-500:focus { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .md\:focus\:border-red-600:focus { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .md\:focus\:border-red-700:focus { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .md\:focus\:border-red-800:focus { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .md\:focus\:border-red-900:focus { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .md\:focus\:border-orange-100:focus { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .md\:focus\:border-orange-200:focus { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .md\:focus\:border-orange-300:focus { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .md\:focus\:border-orange-400:focus { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .md\:focus\:border-orange-500:focus { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .md\:focus\:border-orange-600:focus { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .md\:focus\:border-orange-700:focus { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .md\:focus\:border-orange-800:focus { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .md\:focus\:border-orange-900:focus { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .md\:focus\:border-yellow-100:focus { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .md\:focus\:border-yellow-200:focus { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .md\:focus\:border-yellow-300:focus { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .md\:focus\:border-yellow-400:focus { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .md\:focus\:border-yellow-500:focus { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .md\:focus\:border-yellow-600:focus { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .md\:focus\:border-yellow-700:focus { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .md\:focus\:border-yellow-800:focus { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .md\:focus\:border-yellow-900:focus { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .md\:focus\:border-green-100:focus { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .md\:focus\:border-green-200:focus { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .md\:focus\:border-green-300:focus { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .md\:focus\:border-green-400:focus { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .md\:focus\:border-green-500:focus { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .md\:focus\:border-green-600:focus { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .md\:focus\:border-green-700:focus { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .md\:focus\:border-green-800:focus { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .md\:focus\:border-green-900:focus { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .md\:focus\:border-teal-100:focus { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .md\:focus\:border-teal-200:focus { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .md\:focus\:border-teal-300:focus { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .md\:focus\:border-teal-400:focus { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .md\:focus\:border-teal-500:focus { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .md\:focus\:border-teal-600:focus { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .md\:focus\:border-teal-700:focus { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .md\:focus\:border-teal-800:focus { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .md\:focus\:border-teal-900:focus { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .md\:focus\:border-blue-100:focus { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .md\:focus\:border-blue-200:focus { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .md\:focus\:border-blue-300:focus { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .md\:focus\:border-blue-400:focus { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .md\:focus\:border-blue-500:focus { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .md\:focus\:border-blue-600:focus { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .md\:focus\:border-blue-700:focus { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .md\:focus\:border-blue-800:focus { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .md\:focus\:border-blue-900:focus { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .md\:focus\:border-indigo-100:focus { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .md\:focus\:border-indigo-200:focus { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .md\:focus\:border-indigo-300:focus { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .md\:focus\:border-indigo-400:focus { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .md\:focus\:border-indigo-500:focus { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .md\:focus\:border-indigo-600:focus { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .md\:focus\:border-indigo-700:focus { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .md\:focus\:border-indigo-800:focus { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .md\:focus\:border-indigo-900:focus { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .md\:focus\:border-purple-100:focus { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .md\:focus\:border-purple-200:focus { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .md\:focus\:border-purple-300:focus { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .md\:focus\:border-purple-400:focus { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .md\:focus\:border-purple-500:focus { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .md\:focus\:border-purple-600:focus { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .md\:focus\:border-purple-700:focus { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .md\:focus\:border-purple-800:focus { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .md\:focus\:border-purple-900:focus { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .md\:focus\:border-pink-100:focus { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .md\:focus\:border-pink-200:focus { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .md\:focus\:border-pink-300:focus { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .md\:focus\:border-pink-400:focus { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .md\:focus\:border-pink-500:focus { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .md\:focus\:border-pink-600:focus { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .md\:focus\:border-pink-700:focus { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .md\:focus\:border-pink-800:focus { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .md\:focus\:border-pink-900:focus { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .md\:border-opacity-0 { + --border-opacity: 0; + } + + .md\:border-opacity-25 { + --border-opacity: 0.25; + } + + .md\:border-opacity-50 { + --border-opacity: 0.5; + } + + .md\:border-opacity-75 { + --border-opacity: 0.75; + } + + .md\:border-opacity-100 { + --border-opacity: 1; + } + + .md\:hover\:border-opacity-0:hover { + --border-opacity: 0; + } + + .md\:hover\:border-opacity-25:hover { + --border-opacity: 0.25; + } + + .md\:hover\:border-opacity-50:hover { + --border-opacity: 0.5; + } + + .md\:hover\:border-opacity-75:hover { + --border-opacity: 0.75; + } + + .md\:hover\:border-opacity-100:hover { + --border-opacity: 1; + } + + .md\:focus\:border-opacity-0:focus { + --border-opacity: 0; + } + + .md\:focus\:border-opacity-25:focus { + --border-opacity: 0.25; + } + + .md\:focus\:border-opacity-50:focus { + --border-opacity: 0.5; + } + + .md\:focus\:border-opacity-75:focus { + --border-opacity: 0.75; + } + + .md\:focus\:border-opacity-100:focus { + --border-opacity: 1; + } + + .md\:rounded-none { + border-radius: 0; + } + + .md\:rounded-sm { + border-radius: 0.125rem; + } + + .md\:rounded { + border-radius: 0.25rem; + } + + .md\:rounded-md { + border-radius: 0.375rem; + } + + .md\:rounded-lg { + border-radius: 0.5rem; + } + + .md\:rounded-full { + border-radius: 9999px; + } + + .md\:rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .md\:rounded-r-none { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .md\:rounded-b-none { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + + .md\:rounded-l-none { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .md\:rounded-t-sm { + border-top-left-radius: 0.125rem; + border-top-right-radius: 0.125rem; + } + + .md\:rounded-r-sm { + border-top-right-radius: 0.125rem; + border-bottom-right-radius: 0.125rem; + } + + .md\:rounded-b-sm { + border-bottom-right-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .md\:rounded-l-sm { + border-top-left-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .md\:rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + + .md\:rounded-r { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + } + + .md\:rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .md\:rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .md\:rounded-t-md { + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; + } + + .md\:rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + } + + .md\:rounded-b-md { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .md\:rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .md\:rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } + + .md\:rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + } + + .md\:rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .md\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .md\:rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px; + } + + .md\:rounded-r-full { + border-top-right-radius: 9999px; + border-bottom-right-radius: 9999px; + } + + .md\:rounded-b-full { + border-bottom-right-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .md\:rounded-l-full { + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .md\:rounded-tl-none { + border-top-left-radius: 0; + } + + .md\:rounded-tr-none { + border-top-right-radius: 0; + } + + .md\:rounded-br-none { + border-bottom-right-radius: 0; + } + + .md\:rounded-bl-none { + border-bottom-left-radius: 0; + } + + .md\:rounded-tl-sm { + border-top-left-radius: 0.125rem; + } + + .md\:rounded-tr-sm { + border-top-right-radius: 0.125rem; + } + + .md\:rounded-br-sm { + border-bottom-right-radius: 0.125rem; + } + + .md\:rounded-bl-sm { + border-bottom-left-radius: 0.125rem; + } + + .md\:rounded-tl { + border-top-left-radius: 0.25rem; + } + + .md\:rounded-tr { + border-top-right-radius: 0.25rem; + } + + .md\:rounded-br { + border-bottom-right-radius: 0.25rem; + } + + .md\:rounded-bl { + border-bottom-left-radius: 0.25rem; + } + + .md\:rounded-tl-md { + border-top-left-radius: 0.375rem; + } + + .md\:rounded-tr-md { + border-top-right-radius: 0.375rem; + } + + .md\:rounded-br-md { + border-bottom-right-radius: 0.375rem; + } + + .md\:rounded-bl-md { + border-bottom-left-radius: 0.375rem; + } + + .md\:rounded-tl-lg { + border-top-left-radius: 0.5rem; + } + + .md\:rounded-tr-lg { + border-top-right-radius: 0.5rem; + } + + .md\:rounded-br-lg { + border-bottom-right-radius: 0.5rem; + } + + .md\:rounded-bl-lg { + border-bottom-left-radius: 0.5rem; + } + + .md\:rounded-tl-full { + border-top-left-radius: 9999px; + } + + .md\:rounded-tr-full { + border-top-right-radius: 9999px; + } + + .md\:rounded-br-full { + border-bottom-right-radius: 9999px; + } + + .md\:rounded-bl-full { + border-bottom-left-radius: 9999px; + } + + .md\:border-solid { + border-style: solid; + } + + .md\:border-dashed { + border-style: dashed; + } + + .md\:border-dotted { + border-style: dotted; + } + + .md\:border-double { + border-style: double; + } + + .md\:border-none { + border-style: none; + } + + .md\:border-0 { + border-width: 0; + } + + .md\:border-2 { + border-width: 2px; + } + + .md\:border-4 { + border-width: 4px; + } + + .md\:border-8 { + border-width: 8px; + } + + .md\:border { + border-width: 1px; + } + + .md\:border-t-0 { + border-top-width: 0; + } + + .md\:border-r-0 { + border-right-width: 0; + } + + .md\:border-b-0 { + border-bottom-width: 0; + } + + .md\:border-l-0 { + border-left-width: 0; + } + + .md\:border-t-2 { + border-top-width: 2px; + } + + .md\:border-r-2 { + border-right-width: 2px; + } + + .md\:border-b-2 { + border-bottom-width: 2px; + } + + .md\:border-l-2 { + border-left-width: 2px; + } + + .md\:border-t-4 { + border-top-width: 4px; + } + + .md\:border-r-4 { + border-right-width: 4px; + } + + .md\:border-b-4 { + border-bottom-width: 4px; + } + + .md\:border-l-4 { + border-left-width: 4px; + } + + .md\:border-t-8 { + border-top-width: 8px; + } + + .md\:border-r-8 { + border-right-width: 8px; + } + + .md\:border-b-8 { + border-bottom-width: 8px; + } + + .md\:border-l-8 { + border-left-width: 8px; + } + + .md\:border-t { + border-top-width: 1px; + } + + .md\:border-r { + border-right-width: 1px; + } + + .md\:border-b { + border-bottom-width: 1px; + } + + .md\:border-l { + border-left-width: 1px; + } + + .md\:box-border { + box-sizing: border-box; + } + + .md\:box-content { + box-sizing: content-box; + } + + .md\:cursor-auto { + cursor: auto; + } + + .md\:cursor-default { + cursor: default; + } + + .md\:cursor-pointer { + cursor: pointer; + } + + .md\:cursor-wait { + cursor: wait; + } + + .md\:cursor-text { + cursor: text; + } + + .md\:cursor-move { + cursor: move; + } + + .md\:cursor-not-allowed { + cursor: not-allowed; + } + + .md\:block { + display: block; + } + + .md\:inline-block { + display: inline-block; + } + + .md\:inline { + display: inline; + } + + .md\:flex { + display: flex; + } + + .md\:inline-flex { + display: inline-flex; + } + + .md\:table { + display: table; + } + + .md\:table-caption { + display: table-caption; + } + + .md\:table-cell { + display: table-cell; + } + + .md\:table-column { + display: table-column; + } + + .md\:table-column-group { + display: table-column-group; + } + + .md\:table-footer-group { + display: table-footer-group; + } + + .md\:table-header-group { + display: table-header-group; + } + + .md\:table-row-group { + display: table-row-group; + } + + .md\:table-row { + display: table-row; + } + + .md\:flow-root { + display: flow-root; + } + + .md\:grid { + display: grid; + } + + .md\:inline-grid { + display: inline-grid; + } + + .md\:contents { + display: contents; + } + + .md\:hidden { + display: none; + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:flex-row-reverse { + flex-direction: row-reverse; + } + + .md\:flex-col { + flex-direction: column; + } + + .md\:flex-col-reverse { + flex-direction: column-reverse; + } + + .md\:flex-wrap { + flex-wrap: wrap; + } + + .md\:flex-wrap-reverse { + flex-wrap: wrap-reverse; + } + + .md\:flex-no-wrap { + flex-wrap: nowrap; + } + + .md\:place-items-auto { + place-items: auto; + } + + .md\:place-items-start { + place-items: start; + } + + .md\:place-items-end { + place-items: end; + } + + .md\:place-items-center { + place-items: center; + } + + .md\:place-items-stretch { + place-items: stretch; + } + + .md\:place-content-center { + place-content: center; + } + + .md\:place-content-start { + place-content: start; + } + + .md\:place-content-end { + place-content: end; + } + + .md\:place-content-between { + place-content: space-between; + } + + .md\:place-content-around { + place-content: space-around; + } + + .md\:place-content-evenly { + place-content: space-evenly; + } + + .md\:place-content-stretch { + place-content: stretch; + } + + .md\:place-self-auto { + place-self: auto; + } + + .md\:place-self-start { + place-self: start; + } + + .md\:place-self-end { + place-self: end; + } + + .md\:place-self-center { + place-self: center; + } + + .md\:place-self-stretch { + place-self: stretch; + } + + .md\:items-start { + align-items: flex-start; + } + + .md\:items-end { + align-items: flex-end; + } + + .md\:items-center { + align-items: center; + } + + .md\:items-baseline { + align-items: baseline; + } + + .md\:items-stretch { + align-items: stretch; + } + + .md\:content-center { + align-content: center; + } + + .md\:content-start { + align-content: flex-start; + } + + .md\:content-end { + align-content: flex-end; + } + + .md\:content-between { + align-content: space-between; + } + + .md\:content-around { + align-content: space-around; + } + + .md\:content-evenly { + align-content: space-evenly; + } + + .md\:self-auto { + align-self: auto; + } + + .md\:self-start { + align-self: flex-start; + } + + .md\:self-end { + align-self: flex-end; + } + + .md\:self-center { + align-self: center; + } + + .md\:self-stretch { + align-self: stretch; + } + + .md\:justify-items-auto { + justify-items: auto; + } + + .md\:justify-items-start { + justify-items: start; + } + + .md\:justify-items-end { + justify-items: end; + } + + .md\:justify-items-center { + justify-items: center; + } + + .md\:justify-items-stretch { + justify-items: stretch; + } + + .md\:justify-start { + justify-content: flex-start; + } + + .md\:justify-end { + justify-content: flex-end; + } + + .md\:justify-center { + justify-content: center; + } + + .md\:justify-between { + justify-content: space-between; + } + + .md\:justify-around { + justify-content: space-around; + } + + .md\:justify-evenly { + justify-content: space-evenly; + } + + .md\:justify-self-auto { + justify-self: auto; + } + + .md\:justify-self-start { + justify-self: start; + } + + .md\:justify-self-end { + justify-self: end; + } + + .md\:justify-self-center { + justify-self: center; + } + + .md\:justify-self-stretch { + justify-self: stretch; + } + + .md\:flex-1 { + flex: 1 1 0%; + } + + .md\:flex-auto { + flex: 1 1 auto; + } + + .md\:flex-initial { + flex: 0 1 auto; + } + + .md\:flex-none { + flex: none; + } + + .md\:flex-grow-0 { + flex-grow: 0; + } + + .md\:flex-grow { + flex-grow: 1; + } + + .md\:flex-shrink-0 { + flex-shrink: 0; + } + + .md\:flex-shrink { + flex-shrink: 1; + } + + .md\:order-1 { + order: 1; + } + + .md\:order-2 { + order: 2; + } + + .md\:order-3 { + order: 3; + } + + .md\:order-4 { + order: 4; + } + + .md\:order-5 { + order: 5; + } + + .md\:order-6 { + order: 6; + } + + .md\:order-7 { + order: 7; + } + + .md\:order-8 { + order: 8; + } + + .md\:order-9 { + order: 9; + } + + .md\:order-10 { + order: 10; + } + + .md\:order-11 { + order: 11; + } + + .md\:order-12 { + order: 12; + } + + .md\:order-first { + order: -9999; + } + + .md\:order-last { + order: 9999; + } + + .md\:order-none { + order: 0; + } + + .md\:float-right { + float: right; + } + + .md\:float-left { + float: left; + } + + .md\:float-none { + float: none; + } + + .md\:clearfix:after { + content: ""; + display: table; + clear: both; + } + + .md\:clear-left { + clear: left; + } + + .md\:clear-right { + clear: right; + } + + .md\:clear-both { + clear: both; + } + + .md\:clear-none { + clear: none; + } + + .md\:font-sans { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + } + + .md\:font-serif { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + } + + .md\:font-mono { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .md\:font-hairline { + font-weight: 100; + } + + .md\:font-thin { + font-weight: 200; + } + + .md\:font-light { + font-weight: 300; + } + + .md\:font-normal { + font-weight: 400; + } + + .md\:font-medium { + font-weight: 500; + } + + .md\:font-semibold { + font-weight: 600; + } + + .md\:font-bold { + font-weight: 700; + } + + .md\:font-extrabold { + font-weight: 800; + } + + .md\:font-black { + font-weight: 900; + } + + .md\:hover\:font-hairline:hover { + font-weight: 100; + } + + .md\:hover\:font-thin:hover { + font-weight: 200; + } + + .md\:hover\:font-light:hover { + font-weight: 300; + } + + .md\:hover\:font-normal:hover { + font-weight: 400; + } + + .md\:hover\:font-medium:hover { + font-weight: 500; + } + + .md\:hover\:font-semibold:hover { + font-weight: 600; + } + + .md\:hover\:font-bold:hover { + font-weight: 700; + } + + .md\:hover\:font-extrabold:hover { + font-weight: 800; + } + + .md\:hover\:font-black:hover { + font-weight: 900; + } + + .md\:focus\:font-hairline:focus { + font-weight: 100; + } + + .md\:focus\:font-thin:focus { + font-weight: 200; + } + + .md\:focus\:font-light:focus { + font-weight: 300; + } + + .md\:focus\:font-normal:focus { + font-weight: 400; + } + + .md\:focus\:font-medium:focus { + font-weight: 500; + } + + .md\:focus\:font-semibold:focus { + font-weight: 600; + } + + .md\:focus\:font-bold:focus { + font-weight: 700; + } + + .md\:focus\:font-extrabold:focus { + font-weight: 800; + } + + .md\:focus\:font-black:focus { + font-weight: 900; + } + + .md\:h-0 { + height: 0; + } + + .md\:h-1 { + height: 0.25rem; + } + + .md\:h-2 { + height: 0.5rem; + } + + .md\:h-3 { + height: 0.75rem; + } + + .md\:h-4 { + height: 1rem; + } + + .md\:h-5 { + height: 1.25rem; + } + + .md\:h-6 { + height: 1.5rem; + } + + .md\:h-8 { + height: 2rem; + } + + .md\:h-10 { + height: 2.5rem; + } + + .md\:h-12 { + height: 3rem; + } + + .md\:h-16 { + height: 4rem; + } + + .md\:h-20 { + height: 5rem; + } + + .md\:h-24 { + height: 6rem; + } + + .md\:h-32 { + height: 8rem; + } + + .md\:h-40 { + height: 10rem; + } + + .md\:h-48 { + height: 12rem; + } + + .md\:h-56 { + height: 14rem; + } + + .md\:h-64 { + height: 16rem; + } + + .md\:h-auto { + height: auto; + } + + .md\:h-px { + height: 1px; + } + + .md\:h-full { + height: 100%; + } + + .md\:h-screen { + height: 100vh; + } + + .md\:text-xs { + font-size: 0.75rem; + } + + .md\:text-sm { + font-size: 0.875rem; + } + + .md\:text-base { + font-size: 1rem; + } + + .md\:text-lg { + font-size: 1.125rem; + } + + .md\:text-xl { + font-size: 1.25rem; + } + + .md\:text-2xl { + font-size: 1.5rem; + } + + .md\:text-3xl { + font-size: 1.875rem; + } + + .md\:text-4xl { + font-size: 2.25rem; + } + + .md\:text-5xl { + font-size: 3rem; + } + + .md\:text-6xl { + font-size: 4rem; + } + + .md\:leading-3 { + line-height: .75rem; + } + + .md\:leading-4 { + line-height: 1rem; + } + + .md\:leading-5 { + line-height: 1.25rem; + } + + .md\:leading-6 { + line-height: 1.5rem; + } + + .md\:leading-7 { + line-height: 1.75rem; + } + + .md\:leading-8 { + line-height: 2rem; + } + + .md\:leading-9 { + line-height: 2.25rem; + } + + .md\:leading-10 { + line-height: 2.5rem; + } + + .md\:leading-none { + line-height: 1; + } + + .md\:leading-tight { + line-height: 1.25; + } + + .md\:leading-snug { + line-height: 1.375; + } + + .md\:leading-normal { + line-height: 1.5; + } + + .md\:leading-relaxed { + line-height: 1.625; + } + + .md\:leading-loose { + line-height: 2; + } + + .md\:list-inside { + list-style-position: inside; + } + + .md\:list-outside { + list-style-position: outside; + } + + .md\:list-none { + list-style-type: none; + } + + .md\:list-disc { + list-style-type: disc; + } + + .md\:list-decimal { + list-style-type: decimal; + } + + .md\:m-0 { + margin: 0; + } + + .md\:m-1 { + margin: 0.25rem; + } + + .md\:m-2 { + margin: 0.5rem; + } + + .md\:m-3 { + margin: 0.75rem; + } + + .md\:m-4 { + margin: 1rem; + } + + .md\:m-5 { + margin: 1.25rem; + } + + .md\:m-6 { + margin: 1.5rem; + } + + .md\:m-8 { + margin: 2rem; + } + + .md\:m-10 { + margin: 2.5rem; + } + + .md\:m-12 { + margin: 3rem; + } + + .md\:m-16 { + margin: 4rem; + } + + .md\:m-20 { + margin: 5rem; + } + + .md\:m-24 { + margin: 6rem; + } + + .md\:m-32 { + margin: 8rem; + } + + .md\:m-40 { + margin: 10rem; + } + + .md\:m-48 { + margin: 12rem; + } + + .md\:m-56 { + margin: 14rem; + } + + .md\:m-64 { + margin: 16rem; + } + + .md\:m-auto { + margin: auto; + } + + .md\:m-px { + margin: 1px; + } + + .md\:-m-1 { + margin: -0.25rem; + } + + .md\:-m-2 { + margin: -0.5rem; + } + + .md\:-m-3 { + margin: -0.75rem; + } + + .md\:-m-4 { + margin: -1rem; + } + + .md\:-m-5 { + margin: -1.25rem; + } + + .md\:-m-6 { + margin: -1.5rem; + } + + .md\:-m-8 { + margin: -2rem; + } + + .md\:-m-10 { + margin: -2.5rem; + } + + .md\:-m-12 { + margin: -3rem; + } + + .md\:-m-16 { + margin: -4rem; + } + + .md\:-m-20 { + margin: -5rem; + } + + .md\:-m-24 { + margin: -6rem; + } + + .md\:-m-32 { + margin: -8rem; + } + + .md\:-m-40 { + margin: -10rem; + } + + .md\:-m-48 { + margin: -12rem; + } + + .md\:-m-56 { + margin: -14rem; + } + + .md\:-m-64 { + margin: -16rem; + } + + .md\:-m-px { + margin: -1px; + } + + .md\:my-0 { + margin-top: 0; + margin-bottom: 0; + } + + .md\:mx-0 { + margin-left: 0; + margin-right: 0; + } + + .md\:my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + } + + .md\:mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + + .md\:my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + .md\:mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + + .md\:my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + } + + .md\:mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; + } + + .md\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + .md\:mx-4 { + margin-left: 1rem; + margin-right: 1rem; + } + + .md\:my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + } + + .md\:mx-5 { + margin-left: 1.25rem; + margin-right: 1.25rem; + } + + .md\:my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + + .md\:mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; + } + + .md\:my-8 { + margin-top: 2rem; + margin-bottom: 2rem; + } + + .md\:mx-8 { + margin-left: 2rem; + margin-right: 2rem; + } + + .md\:my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; + } + + .md\:mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; + } + + .md\:my-12 { + margin-top: 3rem; + margin-bottom: 3rem; + } + + .md\:mx-12 { + margin-left: 3rem; + margin-right: 3rem; + } + + .md\:my-16 { + margin-top: 4rem; + margin-bottom: 4rem; + } + + .md\:mx-16 { + margin-left: 4rem; + margin-right: 4rem; + } + + .md\:my-20 { + margin-top: 5rem; + margin-bottom: 5rem; + } + + .md\:mx-20 { + margin-left: 5rem; + margin-right: 5rem; + } + + .md\:my-24 { + margin-top: 6rem; + margin-bottom: 6rem; + } + + .md\:mx-24 { + margin-left: 6rem; + margin-right: 6rem; + } + + .md\:my-32 { + margin-top: 8rem; + margin-bottom: 8rem; + } + + .md\:mx-32 { + margin-left: 8rem; + margin-right: 8rem; + } + + .md\:my-40 { + margin-top: 10rem; + margin-bottom: 10rem; + } + + .md\:mx-40 { + margin-left: 10rem; + margin-right: 10rem; + } + + .md\:my-48 { + margin-top: 12rem; + margin-bottom: 12rem; + } + + .md\:mx-48 { + margin-left: 12rem; + margin-right: 12rem; + } + + .md\:my-56 { + margin-top: 14rem; + margin-bottom: 14rem; + } + + .md\:mx-56 { + margin-left: 14rem; + margin-right: 14rem; + } + + .md\:my-64 { + margin-top: 16rem; + margin-bottom: 16rem; + } + + .md\:mx-64 { + margin-left: 16rem; + margin-right: 16rem; + } + + .md\:my-auto { + margin-top: auto; + margin-bottom: auto; + } + + .md\:mx-auto { + margin-left: auto; + margin-right: auto; + } + + .md\:my-px { + margin-top: 1px; + margin-bottom: 1px; + } + + .md\:mx-px { + margin-left: 1px; + margin-right: 1px; + } + + .md\:-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; + } + + .md\:-mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; + } + + .md\:-my-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; + } + + .md\:-mx-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; + } + + .md\:-my-3 { + margin-top: -0.75rem; + margin-bottom: -0.75rem; + } + + .md\:-mx-3 { + margin-left: -0.75rem; + margin-right: -0.75rem; + } + + .md\:-my-4 { + margin-top: -1rem; + margin-bottom: -1rem; + } + + .md\:-mx-4 { + margin-left: -1rem; + margin-right: -1rem; + } + + .md\:-my-5 { + margin-top: -1.25rem; + margin-bottom: -1.25rem; + } + + .md\:-mx-5 { + margin-left: -1.25rem; + margin-right: -1.25rem; + } + + .md\:-my-6 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; + } + + .md\:-mx-6 { + margin-left: -1.5rem; + margin-right: -1.5rem; + } + + .md\:-my-8 { + margin-top: -2rem; + margin-bottom: -2rem; + } + + .md\:-mx-8 { + margin-left: -2rem; + margin-right: -2rem; + } + + .md\:-my-10 { + margin-top: -2.5rem; + margin-bottom: -2.5rem; + } + + .md\:-mx-10 { + margin-left: -2.5rem; + margin-right: -2.5rem; + } + + .md\:-my-12 { + margin-top: -3rem; + margin-bottom: -3rem; + } + + .md\:-mx-12 { + margin-left: -3rem; + margin-right: -3rem; + } + + .md\:-my-16 { + margin-top: -4rem; + margin-bottom: -4rem; + } + + .md\:-mx-16 { + margin-left: -4rem; + margin-right: -4rem; + } + + .md\:-my-20 { + margin-top: -5rem; + margin-bottom: -5rem; + } + + .md\:-mx-20 { + margin-left: -5rem; + margin-right: -5rem; + } + + .md\:-my-24 { + margin-top: -6rem; + margin-bottom: -6rem; + } + + .md\:-mx-24 { + margin-left: -6rem; + margin-right: -6rem; + } + + .md\:-my-32 { + margin-top: -8rem; + margin-bottom: -8rem; + } + + .md\:-mx-32 { + margin-left: -8rem; + margin-right: -8rem; + } + + .md\:-my-40 { + margin-top: -10rem; + margin-bottom: -10rem; + } + + .md\:-mx-40 { + margin-left: -10rem; + margin-right: -10rem; + } + + .md\:-my-48 { + margin-top: -12rem; + margin-bottom: -12rem; + } + + .md\:-mx-48 { + margin-left: -12rem; + margin-right: -12rem; + } + + .md\:-my-56 { + margin-top: -14rem; + margin-bottom: -14rem; + } + + .md\:-mx-56 { + margin-left: -14rem; + margin-right: -14rem; + } + + .md\:-my-64 { + margin-top: -16rem; + margin-bottom: -16rem; + } + + .md\:-mx-64 { + margin-left: -16rem; + margin-right: -16rem; + } + + .md\:-my-px { + margin-top: -1px; + margin-bottom: -1px; + } + + .md\:-mx-px { + margin-left: -1px; + margin-right: -1px; + } + + .md\:mt-0 { + margin-top: 0; + } + + .md\:mr-0 { + margin-right: 0; + } + + .md\:mb-0 { + margin-bottom: 0; + } + + .md\:ml-0 { + margin-left: 0; + } + + .md\:mt-1 { + margin-top: 0.25rem; + } + + .md\:mr-1 { + margin-right: 0.25rem; + } + + .md\:mb-1 { + margin-bottom: 0.25rem; + } + + .md\:ml-1 { + margin-left: 0.25rem; + } + + .md\:mt-2 { + margin-top: 0.5rem; + } + + .md\:mr-2 { + margin-right: 0.5rem; + } + + .md\:mb-2 { + margin-bottom: 0.5rem; + } + + .md\:ml-2 { + margin-left: 0.5rem; + } + + .md\:mt-3 { + margin-top: 0.75rem; + } + + .md\:mr-3 { + margin-right: 0.75rem; + } + + .md\:mb-3 { + margin-bottom: 0.75rem; + } + + .md\:ml-3 { + margin-left: 0.75rem; + } + + .md\:mt-4 { + margin-top: 1rem; + } + + .md\:mr-4 { + margin-right: 1rem; + } + + .md\:mb-4 { + margin-bottom: 1rem; + } + + .md\:ml-4 { + margin-left: 1rem; + } + + .md\:mt-5 { + margin-top: 1.25rem; + } + + .md\:mr-5 { + margin-right: 1.25rem; + } + + .md\:mb-5 { + margin-bottom: 1.25rem; + } + + .md\:ml-5 { + margin-left: 1.25rem; + } + + .md\:mt-6 { + margin-top: 1.5rem; + } + + .md\:mr-6 { + margin-right: 1.5rem; + } + + .md\:mb-6 { + margin-bottom: 1.5rem; + } + + .md\:ml-6 { + margin-left: 1.5rem; + } + + .md\:mt-8 { + margin-top: 2rem; + } + + .md\:mr-8 { + margin-right: 2rem; + } + + .md\:mb-8 { + margin-bottom: 2rem; + } + + .md\:ml-8 { + margin-left: 2rem; + } + + .md\:mt-10 { + margin-top: 2.5rem; + } + + .md\:mr-10 { + margin-right: 2.5rem; + } + + .md\:mb-10 { + margin-bottom: 2.5rem; + } + + .md\:ml-10 { + margin-left: 2.5rem; + } + + .md\:mt-12 { + margin-top: 3rem; + } + + .md\:mr-12 { + margin-right: 3rem; + } + + .md\:mb-12 { + margin-bottom: 3rem; + } + + .md\:ml-12 { + margin-left: 3rem; + } + + .md\:mt-16 { + margin-top: 4rem; + } + + .md\:mr-16 { + margin-right: 4rem; + } + + .md\:mb-16 { + margin-bottom: 4rem; + } + + .md\:ml-16 { + margin-left: 4rem; + } + + .md\:mt-20 { + margin-top: 5rem; + } + + .md\:mr-20 { + margin-right: 5rem; + } + + .md\:mb-20 { + margin-bottom: 5rem; + } + + .md\:ml-20 { + margin-left: 5rem; + } + + .md\:mt-24 { + margin-top: 6rem; + } + + .md\:mr-24 { + margin-right: 6rem; + } + + .md\:mb-24 { + margin-bottom: 6rem; + } + + .md\:ml-24 { + margin-left: 6rem; + } + + .md\:mt-32 { + margin-top: 8rem; + } + + .md\:mr-32 { + margin-right: 8rem; + } + + .md\:mb-32 { + margin-bottom: 8rem; + } + + .md\:ml-32 { + margin-left: 8rem; + } + + .md\:mt-40 { + margin-top: 10rem; + } + + .md\:mr-40 { + margin-right: 10rem; + } + + .md\:mb-40 { + margin-bottom: 10rem; + } + + .md\:ml-40 { + margin-left: 10rem; + } + + .md\:mt-48 { + margin-top: 12rem; + } + + .md\:mr-48 { + margin-right: 12rem; + } + + .md\:mb-48 { + margin-bottom: 12rem; + } + + .md\:ml-48 { + margin-left: 12rem; + } + + .md\:mt-56 { + margin-top: 14rem; + } + + .md\:mr-56 { + margin-right: 14rem; + } + + .md\:mb-56 { + margin-bottom: 14rem; + } + + .md\:ml-56 { + margin-left: 14rem; + } + + .md\:mt-64 { + margin-top: 16rem; + } + + .md\:mr-64 { + margin-right: 16rem; + } + + .md\:mb-64 { + margin-bottom: 16rem; + } + + .md\:ml-64 { + margin-left: 16rem; + } + + .md\:mt-auto { + margin-top: auto; + } + + .md\:mr-auto { + margin-right: auto; + } + + .md\:mb-auto { + margin-bottom: auto; + } + + .md\:ml-auto { + margin-left: auto; + } + + .md\:mt-px { + margin-top: 1px; + } + + .md\:mr-px { + margin-right: 1px; + } + + .md\:mb-px { + margin-bottom: 1px; + } + + .md\:ml-px { + margin-left: 1px; + } + + .md\:-mt-1 { + margin-top: -0.25rem; + } + + .md\:-mr-1 { + margin-right: -0.25rem; + } + + .md\:-mb-1 { + margin-bottom: -0.25rem; + } + + .md\:-ml-1 { + margin-left: -0.25rem; + } + + .md\:-mt-2 { + margin-top: -0.5rem; + } + + .md\:-mr-2 { + margin-right: -0.5rem; + } + + .md\:-mb-2 { + margin-bottom: -0.5rem; + } + + .md\:-ml-2 { + margin-left: -0.5rem; + } + + .md\:-mt-3 { + margin-top: -0.75rem; + } + + .md\:-mr-3 { + margin-right: -0.75rem; + } + + .md\:-mb-3 { + margin-bottom: -0.75rem; + } + + .md\:-ml-3 { + margin-left: -0.75rem; + } + + .md\:-mt-4 { + margin-top: -1rem; + } + + .md\:-mr-4 { + margin-right: -1rem; + } + + .md\:-mb-4 { + margin-bottom: -1rem; + } + + .md\:-ml-4 { + margin-left: -1rem; + } + + .md\:-mt-5 { + margin-top: -1.25rem; + } + + .md\:-mr-5 { + margin-right: -1.25rem; + } + + .md\:-mb-5 { + margin-bottom: -1.25rem; + } + + .md\:-ml-5 { + margin-left: -1.25rem; + } + + .md\:-mt-6 { + margin-top: -1.5rem; + } + + .md\:-mr-6 { + margin-right: -1.5rem; + } + + .md\:-mb-6 { + margin-bottom: -1.5rem; + } + + .md\:-ml-6 { + margin-left: -1.5rem; + } + + .md\:-mt-8 { + margin-top: -2rem; + } + + .md\:-mr-8 { + margin-right: -2rem; + } + + .md\:-mb-8 { + margin-bottom: -2rem; + } + + .md\:-ml-8 { + margin-left: -2rem; + } + + .md\:-mt-10 { + margin-top: -2.5rem; + } + + .md\:-mr-10 { + margin-right: -2.5rem; + } + + .md\:-mb-10 { + margin-bottom: -2.5rem; + } + + .md\:-ml-10 { + margin-left: -2.5rem; + } + + .md\:-mt-12 { + margin-top: -3rem; + } + + .md\:-mr-12 { + margin-right: -3rem; + } + + .md\:-mb-12 { + margin-bottom: -3rem; + } + + .md\:-ml-12 { + margin-left: -3rem; + } + + .md\:-mt-16 { + margin-top: -4rem; + } + + .md\:-mr-16 { + margin-right: -4rem; + } + + .md\:-mb-16 { + margin-bottom: -4rem; + } + + .md\:-ml-16 { + margin-left: -4rem; + } + + .md\:-mt-20 { + margin-top: -5rem; + } + + .md\:-mr-20 { + margin-right: -5rem; + } + + .md\:-mb-20 { + margin-bottom: -5rem; + } + + .md\:-ml-20 { + margin-left: -5rem; + } + + .md\:-mt-24 { + margin-top: -6rem; + } + + .md\:-mr-24 { + margin-right: -6rem; + } + + .md\:-mb-24 { + margin-bottom: -6rem; + } + + .md\:-ml-24 { + margin-left: -6rem; + } + + .md\:-mt-32 { + margin-top: -8rem; + } + + .md\:-mr-32 { + margin-right: -8rem; + } + + .md\:-mb-32 { + margin-bottom: -8rem; + } + + .md\:-ml-32 { + margin-left: -8rem; + } + + .md\:-mt-40 { + margin-top: -10rem; + } + + .md\:-mr-40 { + margin-right: -10rem; + } + + .md\:-mb-40 { + margin-bottom: -10rem; + } + + .md\:-ml-40 { + margin-left: -10rem; + } + + .md\:-mt-48 { + margin-top: -12rem; + } + + .md\:-mr-48 { + margin-right: -12rem; + } + + .md\:-mb-48 { + margin-bottom: -12rem; + } + + .md\:-ml-48 { + margin-left: -12rem; + } + + .md\:-mt-56 { + margin-top: -14rem; + } + + .md\:-mr-56 { + margin-right: -14rem; + } + + .md\:-mb-56 { + margin-bottom: -14rem; + } + + .md\:-ml-56 { + margin-left: -14rem; + } + + .md\:-mt-64 { + margin-top: -16rem; + } + + .md\:-mr-64 { + margin-right: -16rem; + } + + .md\:-mb-64 { + margin-bottom: -16rem; + } + + .md\:-ml-64 { + margin-left: -16rem; + } + + .md\:-mt-px { + margin-top: -1px; + } + + .md\:-mr-px { + margin-right: -1px; + } + + .md\:-mb-px { + margin-bottom: -1px; + } + + .md\:-ml-px { + margin-left: -1px; + } + + .md\:max-h-full { + max-height: 100%; + } + + .md\:max-h-screen { + max-height: 100vh; + } + + .md\:max-w-none { + max-width: none; + } + + .md\:max-w-xs { + max-width: 20rem; + } + + .md\:max-w-sm { + max-width: 24rem; + } + + .md\:max-w-md { + max-width: 28rem; + } + + .md\:max-w-lg { + max-width: 32rem; + } + + .md\:max-w-xl { + max-width: 36rem; + } + + .md\:max-w-2xl { + max-width: 42rem; + } + + .md\:max-w-3xl { + max-width: 48rem; + } + + .md\:max-w-4xl { + max-width: 56rem; + } + + .md\:max-w-5xl { + max-width: 64rem; + } + + .md\:max-w-6xl { + max-width: 72rem; + } + + .md\:max-w-full { + max-width: 100%; + } + + .md\:max-w-screen-sm { + max-width: 640px; + } + + .md\:max-w-screen-md { + max-width: 768px; + } + + .md\:max-w-screen-lg { + max-width: 1024px; + } + + .md\:max-w-screen-xl { + max-width: 1280px; + } + + .md\:min-h-0 { + min-height: 0; + } + + .md\:min-h-full { + min-height: 100%; + } + + .md\:min-h-screen { + min-height: 100vh; + } + + .md\:min-w-0 { + min-width: 0; + } + + .md\:min-w-full { + min-width: 100%; + } + + .md\:object-contain { + -o-object-fit: contain; + object-fit: contain; + } + + .md\:object-cover { + -o-object-fit: cover; + object-fit: cover; + } + + .md\:object-fill { + -o-object-fit: fill; + object-fit: fill; + } + + .md\:object-none { + -o-object-fit: none; + object-fit: none; + } + + .md\:object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; + } + + .md\:object-bottom { + -o-object-position: bottom; + object-position: bottom; + } + + .md\:object-center { + -o-object-position: center; + object-position: center; + } + + .md\:object-left { + -o-object-position: left; + object-position: left; + } + + .md\:object-left-bottom { + -o-object-position: left bottom; + object-position: left bottom; + } + + .md\:object-left-top { + -o-object-position: left top; + object-position: left top; + } + + .md\:object-right { + -o-object-position: right; + object-position: right; + } + + .md\:object-right-bottom { + -o-object-position: right bottom; + object-position: right bottom; + } + + .md\:object-right-top { + -o-object-position: right top; + object-position: right top; + } + + .md\:object-top { + -o-object-position: top; + object-position: top; + } + + .md\:opacity-0 { + opacity: 0; + } + + .md\:opacity-25 { + opacity: 0.25; + } + + .md\:opacity-50 { + opacity: 0.5; + } + + .md\:opacity-75 { + opacity: 0.75; + } + + .md\:opacity-100 { + opacity: 1; + } + + .md\:hover\:opacity-0:hover { + opacity: 0; + } + + .md\:hover\:opacity-25:hover { + opacity: 0.25; + } + + .md\:hover\:opacity-50:hover { + opacity: 0.5; + } + + .md\:hover\:opacity-75:hover { + opacity: 0.75; + } + + .md\:hover\:opacity-100:hover { + opacity: 1; + } + + .md\:focus\:opacity-0:focus { + opacity: 0; + } + + .md\:focus\:opacity-25:focus { + opacity: 0.25; + } + + .md\:focus\:opacity-50:focus { + opacity: 0.5; + } + + .md\:focus\:opacity-75:focus { + opacity: 0.75; + } + + .md\:focus\:opacity-100:focus { + opacity: 1; + } + + .md\:outline-none { + outline: 0; + } + + .md\:focus\:outline-none:focus { + outline: 0; + } + + .md\:overflow-auto { + overflow: auto; + } + + .md\:overflow-hidden { + overflow: hidden; + } + + .md\:overflow-visible { + overflow: visible; + } + + .md\:overflow-scroll { + overflow: scroll; + } + + .md\:overflow-x-auto { + overflow-x: auto; + } + + .md\:overflow-y-auto { + overflow-y: auto; + } + + .md\:overflow-x-hidden { + overflow-x: hidden; + } + + .md\:overflow-y-hidden { + overflow-y: hidden; + } + + .md\:overflow-x-visible { + overflow-x: visible; + } + + .md\:overflow-y-visible { + overflow-y: visible; + } + + .md\:overflow-x-scroll { + overflow-x: scroll; + } + + .md\:overflow-y-scroll { + overflow-y: scroll; + } + + .md\:scrolling-touch { + -webkit-overflow-scrolling: touch; + } + + .md\:scrolling-auto { + -webkit-overflow-scrolling: auto; + } + + .md\:overscroll-auto { + -ms-scroll-chaining: chained; + overscroll-behavior: auto; + } + + .md\:overscroll-contain { + -ms-scroll-chaining: none; + overscroll-behavior: contain; + } + + .md\:overscroll-none { + -ms-scroll-chaining: none; + overscroll-behavior: none; + } + + .md\:overscroll-y-auto { + overscroll-behavior-y: auto; + } + + .md\:overscroll-y-contain { + overscroll-behavior-y: contain; + } + + .md\:overscroll-y-none { + overscroll-behavior-y: none; + } + + .md\:overscroll-x-auto { + overscroll-behavior-x: auto; + } + + .md\:overscroll-x-contain { + overscroll-behavior-x: contain; + } + + .md\:overscroll-x-none { + overscroll-behavior-x: none; + } + + .md\:p-0 { + padding: 0; + } + + .md\:p-1 { + padding: 0.25rem; + } + + .md\:p-2 { + padding: 0.5rem; + } + + .md\:p-3 { + padding: 0.75rem; + } + + .md\:p-4 { + padding: 1rem; + } + + .md\:p-5 { + padding: 1.25rem; + } + + .md\:p-6 { + padding: 1.5rem; + } + + .md\:p-8 { + padding: 2rem; + } + + .md\:p-10 { + padding: 2.5rem; + } + + .md\:p-12 { + padding: 3rem; + } + + .md\:p-16 { + padding: 4rem; + } + + .md\:p-20 { + padding: 5rem; + } + + .md\:p-24 { + padding: 6rem; + } + + .md\:p-32 { + padding: 8rem; + } + + .md\:p-40 { + padding: 10rem; + } + + .md\:p-48 { + padding: 12rem; + } + + .md\:p-56 { + padding: 14rem; + } + + .md\:p-64 { + padding: 16rem; + } + + .md\:p-px { + padding: 1px; + } + + .md\:py-0 { + padding-top: 0; + padding-bottom: 0; + } + + .md\:px-0 { + padding-left: 0; + padding-right: 0; + } + + .md\:py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } + + .md\:px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + .md\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + .md\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .md\:py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + + .md\:px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + + .md\:py-4 { + padding-top: 1rem; + padding-bottom: 1rem; + } + + .md\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .md\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + + .md\:px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; + } + + .md\:py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; + } + + .md\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .md\:py-8 { + padding-top: 2rem; + padding-bottom: 2rem; + } + + .md\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .md\:py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; + } + + .md\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .md\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + + .md\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .md\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .md\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .md\:py-20 { + padding-top: 5rem; + padding-bottom: 5rem; + } + + .md\:px-20 { + padding-left: 5rem; + padding-right: 5rem; + } + + .md\:py-24 { + padding-top: 6rem; + padding-bottom: 6rem; + } + + .md\:px-24 { + padding-left: 6rem; + padding-right: 6rem; + } + + .md\:py-32 { + padding-top: 8rem; + padding-bottom: 8rem; + } + + .md\:px-32 { + padding-left: 8rem; + padding-right: 8rem; + } + + .md\:py-40 { + padding-top: 10rem; + padding-bottom: 10rem; + } + + .md\:px-40 { + padding-left: 10rem; + padding-right: 10rem; + } + + .md\:py-48 { + padding-top: 12rem; + padding-bottom: 12rem; + } + + .md\:px-48 { + padding-left: 12rem; + padding-right: 12rem; + } + + .md\:py-56 { + padding-top: 14rem; + padding-bottom: 14rem; + } + + .md\:px-56 { + padding-left: 14rem; + padding-right: 14rem; + } + + .md\:py-64 { + padding-top: 16rem; + padding-bottom: 16rem; + } + + .md\:px-64 { + padding-left: 16rem; + padding-right: 16rem; + } + + .md\:py-px { + padding-top: 1px; + padding-bottom: 1px; + } + + .md\:px-px { + padding-left: 1px; + padding-right: 1px; + } + + .md\:pt-0 { + padding-top: 0; + } + + .md\:pr-0 { + padding-right: 0; + } + + .md\:pb-0 { + padding-bottom: 0; + } + + .md\:pl-0 { + padding-left: 0; + } + + .md\:pt-1 { + padding-top: 0.25rem; + } + + .md\:pr-1 { + padding-right: 0.25rem; + } + + .md\:pb-1 { + padding-bottom: 0.25rem; + } + + .md\:pl-1 { + padding-left: 0.25rem; + } + + .md\:pt-2 { + padding-top: 0.5rem; + } + + .md\:pr-2 { + padding-right: 0.5rem; + } + + .md\:pb-2 { + padding-bottom: 0.5rem; + } + + .md\:pl-2 { + padding-left: 0.5rem; + } + + .md\:pt-3 { + padding-top: 0.75rem; + } + + .md\:pr-3 { + padding-right: 0.75rem; + } + + .md\:pb-3 { + padding-bottom: 0.75rem; + } + + .md\:pl-3 { + padding-left: 0.75rem; + } + + .md\:pt-4 { + padding-top: 1rem; + } + + .md\:pr-4 { + padding-right: 1rem; + } + + .md\:pb-4 { + padding-bottom: 1rem; + } + + .md\:pl-4 { + padding-left: 1rem; + } + + .md\:pt-5 { + padding-top: 1.25rem; + } + + .md\:pr-5 { + padding-right: 1.25rem; + } + + .md\:pb-5 { + padding-bottom: 1.25rem; + } + + .md\:pl-5 { + padding-left: 1.25rem; + } + + .md\:pt-6 { + padding-top: 1.5rem; + } + + .md\:pr-6 { + padding-right: 1.5rem; + } + + .md\:pb-6 { + padding-bottom: 1.5rem; + } + + .md\:pl-6 { + padding-left: 1.5rem; + } + + .md\:pt-8 { + padding-top: 2rem; + } + + .md\:pr-8 { + padding-right: 2rem; + } + + .md\:pb-8 { + padding-bottom: 2rem; + } + + .md\:pl-8 { + padding-left: 2rem; + } + + .md\:pt-10 { + padding-top: 2.5rem; + } + + .md\:pr-10 { + padding-right: 2.5rem; + } + + .md\:pb-10 { + padding-bottom: 2.5rem; + } + + .md\:pl-10 { + padding-left: 2.5rem; + } + + .md\:pt-12 { + padding-top: 3rem; + } + + .md\:pr-12 { + padding-right: 3rem; + } + + .md\:pb-12 { + padding-bottom: 3rem; + } + + .md\:pl-12 { + padding-left: 3rem; + } + + .md\:pt-16 { + padding-top: 4rem; + } + + .md\:pr-16 { + padding-right: 4rem; + } + + .md\:pb-16 { + padding-bottom: 4rem; + } + + .md\:pl-16 { + padding-left: 4rem; + } + + .md\:pt-20 { + padding-top: 5rem; + } + + .md\:pr-20 { + padding-right: 5rem; + } + + .md\:pb-20 { + padding-bottom: 5rem; + } + + .md\:pl-20 { + padding-left: 5rem; + } + + .md\:pt-24 { + padding-top: 6rem; + } + + .md\:pr-24 { + padding-right: 6rem; + } + + .md\:pb-24 { + padding-bottom: 6rem; + } + + .md\:pl-24 { + padding-left: 6rem; + } + + .md\:pt-32 { + padding-top: 8rem; + } + + .md\:pr-32 { + padding-right: 8rem; + } + + .md\:pb-32 { + padding-bottom: 8rem; + } + + .md\:pl-32 { + padding-left: 8rem; + } + + .md\:pt-40 { + padding-top: 10rem; + } + + .md\:pr-40 { + padding-right: 10rem; + } + + .md\:pb-40 { + padding-bottom: 10rem; + } + + .md\:pl-40 { + padding-left: 10rem; + } + + .md\:pt-48 { + padding-top: 12rem; + } + + .md\:pr-48 { + padding-right: 12rem; + } + + .md\:pb-48 { + padding-bottom: 12rem; + } + + .md\:pl-48 { + padding-left: 12rem; + } + + .md\:pt-56 { + padding-top: 14rem; + } + + .md\:pr-56 { + padding-right: 14rem; + } + + .md\:pb-56 { + padding-bottom: 14rem; + } + + .md\:pl-56 { + padding-left: 14rem; + } + + .md\:pt-64 { + padding-top: 16rem; + } + + .md\:pr-64 { + padding-right: 16rem; + } + + .md\:pb-64 { + padding-bottom: 16rem; + } + + .md\:pl-64 { + padding-left: 16rem; + } + + .md\:pt-px { + padding-top: 1px; + } + + .md\:pr-px { + padding-right: 1px; + } + + .md\:pb-px { + padding-bottom: 1px; + } + + .md\:pl-px { + padding-left: 1px; + } + + .md\:placeholder-transparent::-moz-placeholder { + color: transparent; + } + + .md\:placeholder-transparent:-ms-input-placeholder { + color: transparent; + } + + .md\:placeholder-transparent::placeholder { + color: transparent; + } + + .md\:placeholder-current::-moz-placeholder { + color: currentColor; + } + + .md\:placeholder-current:-ms-input-placeholder { + color: currentColor; + } + + .md\:placeholder-current::placeholder { + color: currentColor; + } + + .md\:placeholder-black::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:placeholder-black:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:placeholder-black::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:placeholder-white::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-white:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-white::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-100::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-200::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-300::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-500::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-600::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-700::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-800::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-gray-900::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-red-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-red-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-red-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-red-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:placeholder-red-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:placeholder-red-200::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:placeholder-red-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:placeholder-red-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:placeholder-red-300::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:placeholder-red-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:placeholder-red-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:placeholder-red-400::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:placeholder-red-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-red-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-red-500::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-red-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:placeholder-red-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:placeholder-red-600::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:placeholder-red-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:placeholder-red-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:placeholder-red-700::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:placeholder-red-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-red-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-red-800::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:placeholder-red-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:placeholder-red-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:placeholder-red-900::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-100::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-200::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-300::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-400::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-500::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-600::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-700::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-800::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:placeholder-orange-900::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-100::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-200::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-300::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-400::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-500::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-600::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-700::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-800::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:placeholder-yellow-900::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:placeholder-green-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-green-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-green-100::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-green-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-green-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-green-200::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-green-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:placeholder-green-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:placeholder-green-300::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:placeholder-green-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:placeholder-green-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:placeholder-green-400::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:placeholder-green-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:placeholder-green-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:placeholder-green-500::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:placeholder-green-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:placeholder-green-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:placeholder-green-600::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:placeholder-green-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:placeholder-green-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:placeholder-green-700::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:placeholder-green-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:placeholder-green-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:placeholder-green-800::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:placeholder-green-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:placeholder-green-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:placeholder-green-900::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-100::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-200::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-300::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-400::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-500::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-600::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-700::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-800::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:placeholder-teal-900::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-100::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-200::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-300::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-400::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-500::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-600::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-700::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-800::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-blue-900::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-100::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-200::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-300::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-400::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-500::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-600::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-700::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-800::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:placeholder-indigo-900::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-100::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-200::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-300::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-400::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-500::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-600::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-700::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-800::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:placeholder-purple-900::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-200::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-300::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-400::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-500::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-600::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-700::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-800::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:placeholder-pink-900::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-transparent:focus::-moz-placeholder { + color: transparent; + } + + .md\:focus\:placeholder-transparent:focus:-ms-input-placeholder { + color: transparent; + } + + .md\:focus\:placeholder-transparent:focus::placeholder { + color: transparent; + } + + .md\:focus\:placeholder-current:focus::-moz-placeholder { + color: currentColor; + } + + .md\:focus\:placeholder-current:focus:-ms-input-placeholder { + color: currentColor; + } + + .md\:focus\:placeholder-current:focus::placeholder { + color: currentColor; + } + + .md\:focus\:placeholder-black:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-black:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-black:focus::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-white:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-white:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-white:focus::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-200:focus::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-300:focus::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-400:focus::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-500:focus::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-600:focus::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-gray-900:focus::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-300:focus::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-400:focus::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-500:focus::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-600:focus::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-red-900:focus::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-200:focus::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-600:focus::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-orange-900:focus::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-300:focus::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-800:focus::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-yellow-900:focus::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-300:focus::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-400:focus::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-500:focus::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-600:focus::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-800:focus::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-green-900:focus::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-100:focus::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-200:focus::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-300:focus::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-400:focus::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-500:focus::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-600:focus::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-800:focus::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-teal-900:focus::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-200:focus::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-300:focus::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-400:focus::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-500:focus::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-600:focus::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-blue-900:focus::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-300:focus::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-400:focus::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-500:focus::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-600:focus::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-800:focus::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-indigo-900:focus::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-100:focus::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-200:focus::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-300:focus::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-400:focus::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-500:focus::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-600:focus::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-700:focus::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-800:focus::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-purple-900:focus::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-800:focus::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:focus\:placeholder-pink-900:focus::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .md\:placeholder-opacity-0::-moz-placeholder { + --placeholder-opacity: 0; + } + + .md\:placeholder-opacity-0:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .md\:placeholder-opacity-0::placeholder { + --placeholder-opacity: 0; + } + + .md\:placeholder-opacity-25::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .md\:placeholder-opacity-25:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .md\:placeholder-opacity-25::placeholder { + --placeholder-opacity: 0.25; + } + + .md\:placeholder-opacity-50::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .md\:placeholder-opacity-50:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .md\:placeholder-opacity-50::placeholder { + --placeholder-opacity: 0.5; + } + + .md\:placeholder-opacity-75::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .md\:placeholder-opacity-75:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .md\:placeholder-opacity-75::placeholder { + --placeholder-opacity: 0.75; + } + + .md\:placeholder-opacity-100::-moz-placeholder { + --placeholder-opacity: 1; + } + + .md\:placeholder-opacity-100:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .md\:placeholder-opacity-100::placeholder { + --placeholder-opacity: 1; + } + + .md\:focus\:placeholder-opacity-0:focus::-moz-placeholder { + --placeholder-opacity: 0; + } + + .md\:focus\:placeholder-opacity-0:focus:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .md\:focus\:placeholder-opacity-0:focus::placeholder { + --placeholder-opacity: 0; + } + + .md\:focus\:placeholder-opacity-25:focus::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .md\:focus\:placeholder-opacity-25:focus:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .md\:focus\:placeholder-opacity-25:focus::placeholder { + --placeholder-opacity: 0.25; + } + + .md\:focus\:placeholder-opacity-50:focus::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .md\:focus\:placeholder-opacity-50:focus:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .md\:focus\:placeholder-opacity-50:focus::placeholder { + --placeholder-opacity: 0.5; + } + + .md\:focus\:placeholder-opacity-75:focus::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .md\:focus\:placeholder-opacity-75:focus:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .md\:focus\:placeholder-opacity-75:focus::placeholder { + --placeholder-opacity: 0.75; + } + + .md\:focus\:placeholder-opacity-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + } + + .md\:focus\:placeholder-opacity-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .md\:focus\:placeholder-opacity-100:focus::placeholder { + --placeholder-opacity: 1; + } + + .md\:pointer-events-none { + pointer-events: none; + } + + .md\:pointer-events-auto { + pointer-events: auto; + } + + .md\:static { + position: static; + } + + .md\:fixed { + position: fixed; + } + + .md\:absolute { + position: absolute; + } + + .md\:relative { + position: relative; + } + + .md\:sticky { + position: -webkit-sticky; + position: sticky; + } + + .md\:inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .md\:inset-auto { + top: auto; + right: auto; + bottom: auto; + left: auto; + } + + .md\:inset-y-0 { + top: 0; + bottom: 0; + } + + .md\:inset-x-0 { + right: 0; + left: 0; + } + + .md\:inset-y-auto { + top: auto; + bottom: auto; + } + + .md\:inset-x-auto { + right: auto; + left: auto; + } + + .md\:top-0 { + top: 0; + } + + .md\:right-0 { + right: 0; + } + + .md\:bottom-0 { + bottom: 0; + } + + .md\:left-0 { + left: 0; + } + + .md\:top-auto { + top: auto; + } + + .md\:right-auto { + right: auto; + } + + .md\:bottom-auto { + bottom: auto; + } + + .md\:left-auto { + left: auto; + } + + .md\:resize-none { + resize: none; + } + + .md\:resize-y { + resize: vertical; + } + + .md\:resize-x { + resize: horizontal; + } + + .md\:resize { + resize: both; + } + + .md\:shadow-xs { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .md\:shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .md\:shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .md\:shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .md\:shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .md\:shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .md\:shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .md\:shadow-inner { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .md\:shadow-outline { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .md\:shadow-none { + box-shadow: none; + } + + .md\:hover\:shadow-xs:hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .md\:hover\:shadow-sm:hover { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .md\:hover\:shadow:hover { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .md\:hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .md\:hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .md\:hover\:shadow-xl:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .md\:hover\:shadow-2xl:hover { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .md\:hover\:shadow-inner:hover { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .md\:hover\:shadow-outline:hover { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .md\:hover\:shadow-none:hover { + box-shadow: none; + } + + .md\:focus\:shadow-xs:focus { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .md\:focus\:shadow-sm:focus { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .md\:focus\:shadow:focus { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .md\:focus\:shadow-md:focus { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .md\:focus\:shadow-lg:focus { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .md\:focus\:shadow-xl:focus { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .md\:focus\:shadow-2xl:focus { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .md\:focus\:shadow-inner:focus { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .md\:focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .md\:focus\:shadow-none:focus { + box-shadow: none; + } + + .md\:fill-current { + fill: currentColor; + } + + .md\:stroke-current { + stroke: currentColor; + } + + .md\:stroke-0 { + stroke-width: 0; + } + + .md\:stroke-1 { + stroke-width: 1; + } + + .md\:stroke-2 { + stroke-width: 2; + } + + .md\:table-auto { + table-layout: auto; + } + + .md\:table-fixed { + table-layout: fixed; + } + + .md\:text-left { + text-align: left; + } + + .md\:text-center { + text-align: center; + } + + .md\:text-right { + text-align: right; + } + + .md\:text-justify { + text-align: justify; + } + + .md\:text-transparent { + color: transparent; + } + + .md\:text-current { + color: currentColor; + } + + .md\:text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .md\:text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .md\:text-gray-100 { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .md\:text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .md\:text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .md\:text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .md\:text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .md\:text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .md\:text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .md\:text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .md\:text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .md\:text-red-100 { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .md\:text-red-200 { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .md\:text-red-300 { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .md\:text-red-400 { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .md\:text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .md\:text-red-600 { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .md\:text-red-700 { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .md\:text-red-800 { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .md\:text-red-900 { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .md\:text-orange-100 { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .md\:text-orange-200 { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .md\:text-orange-300 { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .md\:text-orange-400 { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .md\:text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .md\:text-orange-600 { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .md\:text-orange-700 { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .md\:text-orange-800 { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .md\:text-orange-900 { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .md\:text-yellow-100 { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .md\:text-yellow-200 { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .md\:text-yellow-300 { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .md\:text-yellow-400 { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .md\:text-yellow-500 { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .md\:text-yellow-600 { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .md\:text-yellow-700 { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .md\:text-yellow-800 { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .md\:text-yellow-900 { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .md\:text-green-100 { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .md\:text-green-200 { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .md\:text-green-300 { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .md\:text-green-400 { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .md\:text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .md\:text-green-600 { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .md\:text-green-700 { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .md\:text-green-800 { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .md\:text-green-900 { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .md\:text-teal-100 { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .md\:text-teal-200 { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .md\:text-teal-300 { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .md\:text-teal-400 { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .md\:text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .md\:text-teal-600 { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .md\:text-teal-700 { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .md\:text-teal-800 { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .md\:text-teal-900 { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .md\:text-blue-100 { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .md\:text-blue-200 { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .md\:text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .md\:text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .md\:text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .md\:text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .md\:text-blue-700 { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .md\:text-blue-800 { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .md\:text-blue-900 { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .md\:text-indigo-100 { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .md\:text-indigo-200 { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .md\:text-indigo-300 { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .md\:text-indigo-400 { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .md\:text-indigo-500 { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .md\:text-indigo-600 { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .md\:text-indigo-700 { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .md\:text-indigo-800 { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .md\:text-indigo-900 { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .md\:text-purple-100 { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .md\:text-purple-200 { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .md\:text-purple-300 { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .md\:text-purple-400 { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .md\:text-purple-500 { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .md\:text-purple-600 { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .md\:text-purple-700 { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .md\:text-purple-800 { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .md\:text-purple-900 { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .md\:text-pink-100 { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .md\:text-pink-200 { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .md\:text-pink-300 { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .md\:text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .md\:text-pink-500 { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .md\:text-pink-600 { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .md\:text-pink-700 { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .md\:text-pink-800 { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .md\:text-pink-900 { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .md\:hover\:text-transparent:hover { + color: transparent; + } + + .md\:hover\:text-current:hover { + color: currentColor; + } + + .md\:hover\:text-black:hover { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .md\:hover\:text-white:hover { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .md\:hover\:text-gray-100:hover { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .md\:hover\:text-gray-200:hover { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .md\:hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .md\:hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .md\:hover\:text-gray-500:hover { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .md\:hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .md\:hover\:text-gray-700:hover { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .md\:hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .md\:hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .md\:hover\:text-red-100:hover { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .md\:hover\:text-red-200:hover { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .md\:hover\:text-red-300:hover { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .md\:hover\:text-red-400:hover { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .md\:hover\:text-red-500:hover { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .md\:hover\:text-red-600:hover { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .md\:hover\:text-red-700:hover { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .md\:hover\:text-red-800:hover { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .md\:hover\:text-red-900:hover { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .md\:hover\:text-orange-100:hover { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .md\:hover\:text-orange-200:hover { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .md\:hover\:text-orange-300:hover { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .md\:hover\:text-orange-400:hover { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .md\:hover\:text-orange-500:hover { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .md\:hover\:text-orange-600:hover { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .md\:hover\:text-orange-700:hover { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .md\:hover\:text-orange-800:hover { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .md\:hover\:text-orange-900:hover { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .md\:hover\:text-yellow-100:hover { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .md\:hover\:text-yellow-200:hover { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .md\:hover\:text-yellow-300:hover { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .md\:hover\:text-yellow-400:hover { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .md\:hover\:text-yellow-500:hover { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .md\:hover\:text-yellow-600:hover { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .md\:hover\:text-yellow-700:hover { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .md\:hover\:text-yellow-800:hover { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .md\:hover\:text-yellow-900:hover { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .md\:hover\:text-green-100:hover { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .md\:hover\:text-green-200:hover { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .md\:hover\:text-green-300:hover { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .md\:hover\:text-green-400:hover { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .md\:hover\:text-green-500:hover { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .md\:hover\:text-green-600:hover { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .md\:hover\:text-green-700:hover { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .md\:hover\:text-green-800:hover { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .md\:hover\:text-green-900:hover { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .md\:hover\:text-teal-100:hover { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .md\:hover\:text-teal-200:hover { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .md\:hover\:text-teal-300:hover { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .md\:hover\:text-teal-400:hover { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .md\:hover\:text-teal-500:hover { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .md\:hover\:text-teal-600:hover { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .md\:hover\:text-teal-700:hover { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .md\:hover\:text-teal-800:hover { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .md\:hover\:text-teal-900:hover { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .md\:hover\:text-blue-100:hover { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .md\:hover\:text-blue-200:hover { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .md\:hover\:text-blue-300:hover { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .md\:hover\:text-blue-400:hover { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .md\:hover\:text-blue-500:hover { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .md\:hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .md\:hover\:text-blue-700:hover { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .md\:hover\:text-blue-800:hover { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .md\:hover\:text-blue-900:hover { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .md\:hover\:text-indigo-100:hover { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .md\:hover\:text-indigo-200:hover { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .md\:hover\:text-indigo-300:hover { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .md\:hover\:text-indigo-400:hover { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .md\:hover\:text-indigo-500:hover { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .md\:hover\:text-indigo-600:hover { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .md\:hover\:text-indigo-700:hover { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .md\:hover\:text-indigo-800:hover { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .md\:hover\:text-indigo-900:hover { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .md\:hover\:text-purple-100:hover { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .md\:hover\:text-purple-200:hover { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .md\:hover\:text-purple-300:hover { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .md\:hover\:text-purple-400:hover { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .md\:hover\:text-purple-500:hover { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .md\:hover\:text-purple-600:hover { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .md\:hover\:text-purple-700:hover { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .md\:hover\:text-purple-800:hover { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .md\:hover\:text-purple-900:hover { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .md\:hover\:text-pink-100:hover { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .md\:hover\:text-pink-200:hover { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .md\:hover\:text-pink-300:hover { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .md\:hover\:text-pink-400:hover { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .md\:hover\:text-pink-500:hover { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .md\:hover\:text-pink-600:hover { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .md\:hover\:text-pink-700:hover { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .md\:hover\:text-pink-800:hover { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .md\:hover\:text-pink-900:hover { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .md\:focus\:text-transparent:focus { + color: transparent; + } + + .md\:focus\:text-current:focus { + color: currentColor; + } + + .md\:focus\:text-black:focus { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .md\:focus\:text-white:focus { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .md\:focus\:text-gray-100:focus { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .md\:focus\:text-gray-200:focus { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .md\:focus\:text-gray-300:focus { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .md\:focus\:text-gray-400:focus { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .md\:focus\:text-gray-500:focus { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .md\:focus\:text-gray-600:focus { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .md\:focus\:text-gray-700:focus { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .md\:focus\:text-gray-800:focus { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .md\:focus\:text-gray-900:focus { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .md\:focus\:text-red-100:focus { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .md\:focus\:text-red-200:focus { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .md\:focus\:text-red-300:focus { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .md\:focus\:text-red-400:focus { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .md\:focus\:text-red-500:focus { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .md\:focus\:text-red-600:focus { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .md\:focus\:text-red-700:focus { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .md\:focus\:text-red-800:focus { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .md\:focus\:text-red-900:focus { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .md\:focus\:text-orange-100:focus { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .md\:focus\:text-orange-200:focus { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .md\:focus\:text-orange-300:focus { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .md\:focus\:text-orange-400:focus { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .md\:focus\:text-orange-500:focus { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .md\:focus\:text-orange-600:focus { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .md\:focus\:text-orange-700:focus { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .md\:focus\:text-orange-800:focus { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .md\:focus\:text-orange-900:focus { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .md\:focus\:text-yellow-100:focus { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .md\:focus\:text-yellow-200:focus { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .md\:focus\:text-yellow-300:focus { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .md\:focus\:text-yellow-400:focus { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .md\:focus\:text-yellow-500:focus { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .md\:focus\:text-yellow-600:focus { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .md\:focus\:text-yellow-700:focus { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .md\:focus\:text-yellow-800:focus { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .md\:focus\:text-yellow-900:focus { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .md\:focus\:text-green-100:focus { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .md\:focus\:text-green-200:focus { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .md\:focus\:text-green-300:focus { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .md\:focus\:text-green-400:focus { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .md\:focus\:text-green-500:focus { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .md\:focus\:text-green-600:focus { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .md\:focus\:text-green-700:focus { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .md\:focus\:text-green-800:focus { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .md\:focus\:text-green-900:focus { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .md\:focus\:text-teal-100:focus { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .md\:focus\:text-teal-200:focus { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .md\:focus\:text-teal-300:focus { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .md\:focus\:text-teal-400:focus { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .md\:focus\:text-teal-500:focus { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .md\:focus\:text-teal-600:focus { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .md\:focus\:text-teal-700:focus { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .md\:focus\:text-teal-800:focus { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .md\:focus\:text-teal-900:focus { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .md\:focus\:text-blue-100:focus { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .md\:focus\:text-blue-200:focus { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .md\:focus\:text-blue-300:focus { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .md\:focus\:text-blue-400:focus { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .md\:focus\:text-blue-500:focus { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .md\:focus\:text-blue-600:focus { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .md\:focus\:text-blue-700:focus { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .md\:focus\:text-blue-800:focus { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .md\:focus\:text-blue-900:focus { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .md\:focus\:text-indigo-100:focus { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .md\:focus\:text-indigo-200:focus { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .md\:focus\:text-indigo-300:focus { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .md\:focus\:text-indigo-400:focus { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .md\:focus\:text-indigo-500:focus { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .md\:focus\:text-indigo-600:focus { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .md\:focus\:text-indigo-700:focus { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .md\:focus\:text-indigo-800:focus { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .md\:focus\:text-indigo-900:focus { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .md\:focus\:text-purple-100:focus { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .md\:focus\:text-purple-200:focus { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .md\:focus\:text-purple-300:focus { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .md\:focus\:text-purple-400:focus { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .md\:focus\:text-purple-500:focus { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .md\:focus\:text-purple-600:focus { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .md\:focus\:text-purple-700:focus { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .md\:focus\:text-purple-800:focus { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .md\:focus\:text-purple-900:focus { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .md\:focus\:text-pink-100:focus { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .md\:focus\:text-pink-200:focus { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .md\:focus\:text-pink-300:focus { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .md\:focus\:text-pink-400:focus { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .md\:focus\:text-pink-500:focus { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .md\:focus\:text-pink-600:focus { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .md\:focus\:text-pink-700:focus { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .md\:focus\:text-pink-800:focus { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .md\:focus\:text-pink-900:focus { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .md\:text-opacity-0 { + --text-opacity: 0; + } + + .md\:text-opacity-25 { + --text-opacity: 0.25; + } + + .md\:text-opacity-50 { + --text-opacity: 0.5; + } + + .md\:text-opacity-75 { + --text-opacity: 0.75; + } + + .md\:text-opacity-100 { + --text-opacity: 1; + } + + .md\:hover\:text-opacity-0:hover { + --text-opacity: 0; + } + + .md\:hover\:text-opacity-25:hover { + --text-opacity: 0.25; + } + + .md\:hover\:text-opacity-50:hover { + --text-opacity: 0.5; + } + + .md\:hover\:text-opacity-75:hover { + --text-opacity: 0.75; + } + + .md\:hover\:text-opacity-100:hover { + --text-opacity: 1; + } + + .md\:focus\:text-opacity-0:focus { + --text-opacity: 0; + } + + .md\:focus\:text-opacity-25:focus { + --text-opacity: 0.25; + } + + .md\:focus\:text-opacity-50:focus { + --text-opacity: 0.5; + } + + .md\:focus\:text-opacity-75:focus { + --text-opacity: 0.75; + } + + .md\:focus\:text-opacity-100:focus { + --text-opacity: 1; + } + + .md\:italic { + font-style: italic; + } + + .md\:not-italic { + font-style: normal; + } + + .md\:uppercase { + text-transform: uppercase; + } + + .md\:lowercase { + text-transform: lowercase; + } + + .md\:capitalize { + text-transform: capitalize; + } + + .md\:normal-case { + text-transform: none; + } + + .md\:underline { + text-decoration: underline; + } + + .md\:line-through { + text-decoration: line-through; + } + + .md\:no-underline { + text-decoration: none; + } + + .md\:hover\:underline:hover { + text-decoration: underline; + } + + .md\:hover\:line-through:hover { + text-decoration: line-through; + } + + .md\:hover\:no-underline:hover { + text-decoration: none; + } + + .md\:focus\:underline:focus { + text-decoration: underline; + } + + .md\:focus\:line-through:focus { + text-decoration: line-through; + } + + .md\:focus\:no-underline:focus { + text-decoration: none; + } + + .md\:antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .md\:subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + } + + .md\:ordinal, .md\:slashed-zero, .md\:lining-nums, .md\:oldstyle-nums, .md\:proportional-nums, .md\:tabular-nums, .md\:diagonal-fractions, .md\:stacked-fractions { + --font-variant-numeric-ordinal: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-slashed-zero: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-figure: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-spacing: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-fraction: var(--tailwind-empty,/*!*/ /*!*/); + font-variant-numeric: var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction); + } + + .md\:normal-nums { + font-variant-numeric: normal; + } + + .md\:ordinal { + --font-variant-numeric-ordinal: ordinal; + } + + .md\:slashed-zero { + --font-variant-numeric-slashed-zero: slashed-zero; + } + + .md\:lining-nums { + --font-variant-numeric-figure: lining-nums; + } + + .md\:oldstyle-nums { + --font-variant-numeric-figure: oldstyle-nums; + } + + .md\:proportional-nums { + --font-variant-numeric-spacing: proportional-nums; + } + + .md\:tabular-nums { + --font-variant-numeric-spacing: tabular-nums; + } + + .md\:diagonal-fractions { + --font-variant-numeric-fraction: diagonal-fractions; + } + + .md\:stacked-fractions { + --font-variant-numeric-fraction: stacked-fractions; + } + + .md\:tracking-tighter { + letter-spacing: -0.05em; + } + + .md\:tracking-tight { + letter-spacing: -0.025em; + } + + .md\:tracking-normal { + letter-spacing: 0; + } + + .md\:tracking-wide { + letter-spacing: 0.025em; + } + + .md\:tracking-wider { + letter-spacing: 0.05em; + } + + .md\:tracking-widest { + letter-spacing: 0.1em; + } + + .md\:select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .md\:select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .md\:select-all { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + } + + .md\:select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; + } + + .md\:align-baseline { + vertical-align: baseline; + } + + .md\:align-top { + vertical-align: top; + } + + .md\:align-middle { + vertical-align: middle; + } + + .md\:align-bottom { + vertical-align: bottom; + } + + .md\:align-text-top { + vertical-align: text-top; + } + + .md\:align-text-bottom { + vertical-align: text-bottom; + } + + .md\:visible { + visibility: visible; + } + + .md\:invisible { + visibility: hidden; + } + + .md\:whitespace-normal { + white-space: normal; + } + + .md\:whitespace-no-wrap { + white-space: nowrap; + } + + .md\:whitespace-pre { + white-space: pre; + } + + .md\:whitespace-pre-line { + white-space: pre-line; + } + + .md\:whitespace-pre-wrap { + white-space: pre-wrap; + } + + .md\:break-normal { + overflow-wrap: normal; + word-break: normal; + } + + .md\:break-words { + overflow-wrap: break-word; + } + + .md\:break-all { + word-break: break-all; + } + + .md\:truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .md\:w-0 { + width: 0; + } + + .md\:w-1 { + width: 0.25rem; + } + + .md\:w-2 { + width: 0.5rem; + } + + .md\:w-3 { + width: 0.75rem; + } + + .md\:w-4 { + width: 1rem; + } + + .md\:w-5 { + width: 1.25rem; + } + + .md\:w-6 { + width: 1.5rem; + } + + .md\:w-8 { + width: 2rem; + } + + .md\:w-10 { + width: 2.5rem; + } + + .md\:w-12 { + width: 3rem; + } + + .md\:w-16 { + width: 4rem; + } + + .md\:w-20 { + width: 5rem; + } + + .md\:w-24 { + width: 6rem; + } + + .md\:w-32 { + width: 8rem; + } + + .md\:w-40 { + width: 10rem; + } + + .md\:w-48 { + width: 12rem; + } + + .md\:w-56 { + width: 14rem; + } + + .md\:w-64 { + width: 16rem; + } + + .md\:w-auto { + width: auto; + } + + .md\:w-px { + width: 1px; + } + + .md\:w-1\/2 { + width: 50%; + } + + .md\:w-1\/3 { + width: 33.333333%; + } + + .md\:w-2\/3 { + width: 66.666667%; + } + + .md\:w-1\/4 { + width: 25%; + } + + .md\:w-2\/4 { + width: 50%; + } + + .md\:w-3\/4 { + width: 75%; + } + + .md\:w-1\/5 { + width: 20%; + } + + .md\:w-2\/5 { + width: 40%; + } + + .md\:w-3\/5 { + width: 60%; + } + + .md\:w-4\/5 { + width: 80%; + } + + .md\:w-1\/6 { + width: 16.666667%; + } + + .md\:w-2\/6 { + width: 33.333333%; + } + + .md\:w-3\/6 { + width: 50%; + } + + .md\:w-4\/6 { + width: 66.666667%; + } + + .md\:w-5\/6 { + width: 83.333333%; + } + + .md\:w-1\/12 { + width: 8.333333%; + } + + .md\:w-2\/12 { + width: 16.666667%; + } + + .md\:w-3\/12 { + width: 25%; + } + + .md\:w-4\/12 { + width: 33.333333%; + } + + .md\:w-5\/12 { + width: 41.666667%; + } + + .md\:w-6\/12 { + width: 50%; + } + + .md\:w-7\/12 { + width: 58.333333%; + } + + .md\:w-8\/12 { + width: 66.666667%; + } + + .md\:w-9\/12 { + width: 75%; + } + + .md\:w-10\/12 { + width: 83.333333%; + } + + .md\:w-11\/12 { + width: 91.666667%; + } + + .md\:w-full { + width: 100%; + } + + .md\:w-screen { + width: 100vw; + } + + .md\:z-0 { + z-index: 0; + } + + .md\:z-10 { + z-index: 10; + } + + .md\:z-20 { + z-index: 20; + } + + .md\:z-30 { + z-index: 30; + } + + .md\:z-40 { + z-index: 40; + } + + .md\:z-50 { + z-index: 50; + } + + .md\:z-auto { + z-index: auto; + } + + .md\:gap-0 { + grid-gap: 0; + gap: 0; + } + + .md\:gap-1 { + grid-gap: 0.25rem; + gap: 0.25rem; + } + + .md\:gap-2 { + grid-gap: 0.5rem; + gap: 0.5rem; + } + + .md\:gap-3 { + grid-gap: 0.75rem; + gap: 0.75rem; + } + + .md\:gap-4 { + grid-gap: 1rem; + gap: 1rem; + } + + .md\:gap-5 { + grid-gap: 1.25rem; + gap: 1.25rem; + } + + .md\:gap-6 { + grid-gap: 1.5rem; + gap: 1.5rem; + } + + .md\:gap-8 { + grid-gap: 2rem; + gap: 2rem; + } + + .md\:gap-10 { + grid-gap: 2.5rem; + gap: 2.5rem; + } + + .md\:gap-12 { + grid-gap: 3rem; + gap: 3rem; + } + + .md\:gap-16 { + grid-gap: 4rem; + gap: 4rem; + } + + .md\:gap-20 { + grid-gap: 5rem; + gap: 5rem; + } + + .md\:gap-24 { + grid-gap: 6rem; + gap: 6rem; + } + + .md\:gap-32 { + grid-gap: 8rem; + gap: 8rem; + } + + .md\:gap-40 { + grid-gap: 10rem; + gap: 10rem; + } + + .md\:gap-48 { + grid-gap: 12rem; + gap: 12rem; + } + + .md\:gap-56 { + grid-gap: 14rem; + gap: 14rem; + } + + .md\:gap-64 { + grid-gap: 16rem; + gap: 16rem; + } + + .md\:gap-px { + grid-gap: 1px; + gap: 1px; + } + + .md\:col-gap-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .md\:col-gap-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .md\:col-gap-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .md\:col-gap-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .md\:col-gap-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .md\:col-gap-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .md\:col-gap-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .md\:col-gap-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .md\:col-gap-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .md\:col-gap-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .md\:col-gap-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .md\:col-gap-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .md\:col-gap-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .md\:col-gap-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .md\:col-gap-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .md\:col-gap-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .md\:col-gap-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .md\:col-gap-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .md\:col-gap-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .md\:gap-x-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .md\:gap-x-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .md\:gap-x-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .md\:gap-x-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .md\:gap-x-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .md\:gap-x-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .md\:gap-x-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .md\:gap-x-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .md\:gap-x-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .md\:gap-x-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .md\:gap-x-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .md\:gap-x-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .md\:gap-x-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .md\:gap-x-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .md\:gap-x-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .md\:gap-x-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .md\:gap-x-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .md\:gap-x-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .md\:gap-x-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .md\:row-gap-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .md\:row-gap-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .md\:row-gap-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .md\:row-gap-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .md\:row-gap-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .md\:row-gap-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .md\:row-gap-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .md\:row-gap-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .md\:row-gap-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .md\:row-gap-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .md\:row-gap-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .md\:row-gap-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .md\:row-gap-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .md\:row-gap-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .md\:row-gap-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .md\:row-gap-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .md\:row-gap-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .md\:row-gap-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .md\:row-gap-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .md\:gap-y-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .md\:gap-y-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .md\:gap-y-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .md\:gap-y-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .md\:gap-y-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .md\:gap-y-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .md\:gap-y-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .md\:gap-y-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .md\:gap-y-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .md\:gap-y-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .md\:gap-y-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .md\:gap-y-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .md\:gap-y-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .md\:gap-y-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .md\:gap-y-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .md\:gap-y-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .md\:gap-y-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .md\:gap-y-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .md\:gap-y-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .md\:grid-flow-row { + grid-auto-flow: row; + } + + .md\:grid-flow-col { + grid-auto-flow: column; + } + + .md\:grid-flow-row-dense { + grid-auto-flow: row dense; + } + + .md\:grid-flow-col-dense { + grid-auto-flow: column dense; + } + + .md\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .md\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .md\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .md\:grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + .md\:grid-cols-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + + .md\:grid-cols-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + + .md\:grid-cols-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + + .md\:grid-cols-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + + .md\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + + .md\:grid-cols-none { + grid-template-columns: none; + } + + .md\:col-auto { + grid-column: auto; + } + + .md\:col-span-1 { + grid-column: span 1 / span 1; + } + + .md\:col-span-2 { + grid-column: span 2 / span 2; + } + + .md\:col-span-3 { + grid-column: span 3 / span 3; + } + + .md\:col-span-4 { + grid-column: span 4 / span 4; + } + + .md\:col-span-5 { + grid-column: span 5 / span 5; + } + + .md\:col-span-6 { + grid-column: span 6 / span 6; + } + + .md\:col-span-7 { + grid-column: span 7 / span 7; + } + + .md\:col-span-8 { + grid-column: span 8 / span 8; + } + + .md\:col-span-9 { + grid-column: span 9 / span 9; + } + + .md\:col-span-10 { + grid-column: span 10 / span 10; + } + + .md\:col-span-11 { + grid-column: span 11 / span 11; + } + + .md\:col-span-12 { + grid-column: span 12 / span 12; + } + + .md\:col-start-1 { + grid-column-start: 1; + } + + .md\:col-start-2 { + grid-column-start: 2; + } + + .md\:col-start-3 { + grid-column-start: 3; + } + + .md\:col-start-4 { + grid-column-start: 4; + } + + .md\:col-start-5 { + grid-column-start: 5; + } + + .md\:col-start-6 { + grid-column-start: 6; + } + + .md\:col-start-7 { + grid-column-start: 7; + } + + .md\:col-start-8 { + grid-column-start: 8; + } + + .md\:col-start-9 { + grid-column-start: 9; + } + + .md\:col-start-10 { + grid-column-start: 10; + } + + .md\:col-start-11 { + grid-column-start: 11; + } + + .md\:col-start-12 { + grid-column-start: 12; + } + + .md\:col-start-13 { + grid-column-start: 13; + } + + .md\:col-start-auto { + grid-column-start: auto; + } + + .md\:col-end-1 { + grid-column-end: 1; + } + + .md\:col-end-2 { + grid-column-end: 2; + } + + .md\:col-end-3 { + grid-column-end: 3; + } + + .md\:col-end-4 { + grid-column-end: 4; + } + + .md\:col-end-5 { + grid-column-end: 5; + } + + .md\:col-end-6 { + grid-column-end: 6; + } + + .md\:col-end-7 { + grid-column-end: 7; + } + + .md\:col-end-8 { + grid-column-end: 8; + } + + .md\:col-end-9 { + grid-column-end: 9; + } + + .md\:col-end-10 { + grid-column-end: 10; + } + + .md\:col-end-11 { + grid-column-end: 11; + } + + .md\:col-end-12 { + grid-column-end: 12; + } + + .md\:col-end-13 { + grid-column-end: 13; + } + + .md\:col-end-auto { + grid-column-end: auto; + } + + .md\:grid-rows-1 { + grid-template-rows: repeat(1, minmax(0, 1fr)); + } + + .md\:grid-rows-2 { + grid-template-rows: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-rows-3 { + grid-template-rows: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-rows-4 { + grid-template-rows: repeat(4, minmax(0, 1fr)); + } + + .md\:grid-rows-5 { + grid-template-rows: repeat(5, minmax(0, 1fr)); + } + + .md\:grid-rows-6 { + grid-template-rows: repeat(6, minmax(0, 1fr)); + } + + .md\:grid-rows-none { + grid-template-rows: none; + } + + .md\:row-auto { + grid-row: auto; + } + + .md\:row-span-1 { + grid-row: span 1 / span 1; + } + + .md\:row-span-2 { + grid-row: span 2 / span 2; + } + + .md\:row-span-3 { + grid-row: span 3 / span 3; + } + + .md\:row-span-4 { + grid-row: span 4 / span 4; + } + + .md\:row-span-5 { + grid-row: span 5 / span 5; + } + + .md\:row-span-6 { + grid-row: span 6 / span 6; + } + + .md\:row-start-1 { + grid-row-start: 1; + } + + .md\:row-start-2 { + grid-row-start: 2; + } + + .md\:row-start-3 { + grid-row-start: 3; + } + + .md\:row-start-4 { + grid-row-start: 4; + } + + .md\:row-start-5 { + grid-row-start: 5; + } + + .md\:row-start-6 { + grid-row-start: 6; + } + + .md\:row-start-7 { + grid-row-start: 7; + } + + .md\:row-start-auto { + grid-row-start: auto; + } + + .md\:row-end-1 { + grid-row-end: 1; + } + + .md\:row-end-2 { + grid-row-end: 2; + } + + .md\:row-end-3 { + grid-row-end: 3; + } + + .md\:row-end-4 { + grid-row-end: 4; + } + + .md\:row-end-5 { + grid-row-end: 5; + } + + .md\:row-end-6 { + grid-row-end: 6; + } + + .md\:row-end-7 { + grid-row-end: 7; + } + + .md\:row-end-auto { + grid-row-end: auto; + } + + .md\:transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); + } + + .md\:transform-none { + transform: none; + } + + .md\:origin-center { + transform-origin: center; + } + + .md\:origin-top { + transform-origin: top; + } + + .md\:origin-top-right { + transform-origin: top right; + } + + .md\:origin-right { + transform-origin: right; + } + + .md\:origin-bottom-right { + transform-origin: bottom right; + } + + .md\:origin-bottom { + transform-origin: bottom; + } + + .md\:origin-bottom-left { + transform-origin: bottom left; + } + + .md\:origin-left { + transform-origin: left; + } + + .md\:origin-top-left { + transform-origin: top left; + } + + .md\:scale-0 { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .md\:scale-50 { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .md\:scale-75 { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .md\:scale-90 { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .md\:scale-95 { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .md\:scale-100 { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .md\:scale-105 { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .md\:scale-110 { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .md\:scale-125 { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .md\:scale-150 { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .md\:scale-x-0 { + --transform-scale-x: 0; + } + + .md\:scale-x-50 { + --transform-scale-x: .5; + } + + .md\:scale-x-75 { + --transform-scale-x: .75; + } + + .md\:scale-x-90 { + --transform-scale-x: .9; + } + + .md\:scale-x-95 { + --transform-scale-x: .95; + } + + .md\:scale-x-100 { + --transform-scale-x: 1; + } + + .md\:scale-x-105 { + --transform-scale-x: 1.05; + } + + .md\:scale-x-110 { + --transform-scale-x: 1.1; + } + + .md\:scale-x-125 { + --transform-scale-x: 1.25; + } + + .md\:scale-x-150 { + --transform-scale-x: 1.5; + } + + .md\:scale-y-0 { + --transform-scale-y: 0; + } + + .md\:scale-y-50 { + --transform-scale-y: .5; + } + + .md\:scale-y-75 { + --transform-scale-y: .75; + } + + .md\:scale-y-90 { + --transform-scale-y: .9; + } + + .md\:scale-y-95 { + --transform-scale-y: .95; + } + + .md\:scale-y-100 { + --transform-scale-y: 1; + } + + .md\:scale-y-105 { + --transform-scale-y: 1.05; + } + + .md\:scale-y-110 { + --transform-scale-y: 1.1; + } + + .md\:scale-y-125 { + --transform-scale-y: 1.25; + } + + .md\:scale-y-150 { + --transform-scale-y: 1.5; + } + + .md\:hover\:scale-0:hover { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .md\:hover\:scale-50:hover { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .md\:hover\:scale-75:hover { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .md\:hover\:scale-90:hover { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .md\:hover\:scale-95:hover { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .md\:hover\:scale-100:hover { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .md\:hover\:scale-105:hover { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .md\:hover\:scale-110:hover { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .md\:hover\:scale-125:hover { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .md\:hover\:scale-150:hover { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .md\:hover\:scale-x-0:hover { + --transform-scale-x: 0; + } + + .md\:hover\:scale-x-50:hover { + --transform-scale-x: .5; + } + + .md\:hover\:scale-x-75:hover { + --transform-scale-x: .75; + } + + .md\:hover\:scale-x-90:hover { + --transform-scale-x: .9; + } + + .md\:hover\:scale-x-95:hover { + --transform-scale-x: .95; + } + + .md\:hover\:scale-x-100:hover { + --transform-scale-x: 1; + } + + .md\:hover\:scale-x-105:hover { + --transform-scale-x: 1.05; + } + + .md\:hover\:scale-x-110:hover { + --transform-scale-x: 1.1; + } + + .md\:hover\:scale-x-125:hover { + --transform-scale-x: 1.25; + } + + .md\:hover\:scale-x-150:hover { + --transform-scale-x: 1.5; + } + + .md\:hover\:scale-y-0:hover { + --transform-scale-y: 0; + } + + .md\:hover\:scale-y-50:hover { + --transform-scale-y: .5; + } + + .md\:hover\:scale-y-75:hover { + --transform-scale-y: .75; + } + + .md\:hover\:scale-y-90:hover { + --transform-scale-y: .9; + } + + .md\:hover\:scale-y-95:hover { + --transform-scale-y: .95; + } + + .md\:hover\:scale-y-100:hover { + --transform-scale-y: 1; + } + + .md\:hover\:scale-y-105:hover { + --transform-scale-y: 1.05; + } + + .md\:hover\:scale-y-110:hover { + --transform-scale-y: 1.1; + } + + .md\:hover\:scale-y-125:hover { + --transform-scale-y: 1.25; + } + + .md\:hover\:scale-y-150:hover { + --transform-scale-y: 1.5; + } + + .md\:focus\:scale-0:focus { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .md\:focus\:scale-50:focus { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .md\:focus\:scale-75:focus { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .md\:focus\:scale-90:focus { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .md\:focus\:scale-95:focus { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .md\:focus\:scale-100:focus { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .md\:focus\:scale-105:focus { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .md\:focus\:scale-110:focus { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .md\:focus\:scale-125:focus { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .md\:focus\:scale-150:focus { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .md\:focus\:scale-x-0:focus { + --transform-scale-x: 0; + } + + .md\:focus\:scale-x-50:focus { + --transform-scale-x: .5; + } + + .md\:focus\:scale-x-75:focus { + --transform-scale-x: .75; + } + + .md\:focus\:scale-x-90:focus { + --transform-scale-x: .9; + } + + .md\:focus\:scale-x-95:focus { + --transform-scale-x: .95; + } + + .md\:focus\:scale-x-100:focus { + --transform-scale-x: 1; + } + + .md\:focus\:scale-x-105:focus { + --transform-scale-x: 1.05; + } + + .md\:focus\:scale-x-110:focus { + --transform-scale-x: 1.1; + } + + .md\:focus\:scale-x-125:focus { + --transform-scale-x: 1.25; + } + + .md\:focus\:scale-x-150:focus { + --transform-scale-x: 1.5; + } + + .md\:focus\:scale-y-0:focus { + --transform-scale-y: 0; + } + + .md\:focus\:scale-y-50:focus { + --transform-scale-y: .5; + } + + .md\:focus\:scale-y-75:focus { + --transform-scale-y: .75; + } + + .md\:focus\:scale-y-90:focus { + --transform-scale-y: .9; + } + + .md\:focus\:scale-y-95:focus { + --transform-scale-y: .95; + } + + .md\:focus\:scale-y-100:focus { + --transform-scale-y: 1; + } + + .md\:focus\:scale-y-105:focus { + --transform-scale-y: 1.05; + } + + .md\:focus\:scale-y-110:focus { + --transform-scale-y: 1.1; + } + + .md\:focus\:scale-y-125:focus { + --transform-scale-y: 1.25; + } + + .md\:focus\:scale-y-150:focus { + --transform-scale-y: 1.5; + } + + .md\:rotate-0 { + --transform-rotate: 0; + } + + .md\:rotate-45 { + --transform-rotate: 45deg; + } + + .md\:rotate-90 { + --transform-rotate: 90deg; + } + + .md\:rotate-180 { + --transform-rotate: 180deg; + } + + .md\:-rotate-180 { + --transform-rotate: -180deg; + } + + .md\:-rotate-90 { + --transform-rotate: -90deg; + } + + .md\:-rotate-45 { + --transform-rotate: -45deg; + } + + .md\:hover\:rotate-0:hover { + --transform-rotate: 0; + } + + .md\:hover\:rotate-45:hover { + --transform-rotate: 45deg; + } + + .md\:hover\:rotate-90:hover { + --transform-rotate: 90deg; + } + + .md\:hover\:rotate-180:hover { + --transform-rotate: 180deg; + } + + .md\:hover\:-rotate-180:hover { + --transform-rotate: -180deg; + } + + .md\:hover\:-rotate-90:hover { + --transform-rotate: -90deg; + } + + .md\:hover\:-rotate-45:hover { + --transform-rotate: -45deg; + } + + .md\:focus\:rotate-0:focus { + --transform-rotate: 0; + } + + .md\:focus\:rotate-45:focus { + --transform-rotate: 45deg; + } + + .md\:focus\:rotate-90:focus { + --transform-rotate: 90deg; + } + + .md\:focus\:rotate-180:focus { + --transform-rotate: 180deg; + } + + .md\:focus\:-rotate-180:focus { + --transform-rotate: -180deg; + } + + .md\:focus\:-rotate-90:focus { + --transform-rotate: -90deg; + } + + .md\:focus\:-rotate-45:focus { + --transform-rotate: -45deg; + } + + .md\:translate-x-0 { + --transform-translate-x: 0; + } + + .md\:translate-x-1 { + --transform-translate-x: 0.25rem; + } + + .md\:translate-x-2 { + --transform-translate-x: 0.5rem; + } + + .md\:translate-x-3 { + --transform-translate-x: 0.75rem; + } + + .md\:translate-x-4 { + --transform-translate-x: 1rem; + } + + .md\:translate-x-5 { + --transform-translate-x: 1.25rem; + } + + .md\:translate-x-6 { + --transform-translate-x: 1.5rem; + } + + .md\:translate-x-8 { + --transform-translate-x: 2rem; + } + + .md\:translate-x-10 { + --transform-translate-x: 2.5rem; + } + + .md\:translate-x-12 { + --transform-translate-x: 3rem; + } + + .md\:translate-x-16 { + --transform-translate-x: 4rem; + } + + .md\:translate-x-20 { + --transform-translate-x: 5rem; + } + + .md\:translate-x-24 { + --transform-translate-x: 6rem; + } + + .md\:translate-x-32 { + --transform-translate-x: 8rem; + } + + .md\:translate-x-40 { + --transform-translate-x: 10rem; + } + + .md\:translate-x-48 { + --transform-translate-x: 12rem; + } + + .md\:translate-x-56 { + --transform-translate-x: 14rem; + } + + .md\:translate-x-64 { + --transform-translate-x: 16rem; + } + + .md\:translate-x-px { + --transform-translate-x: 1px; + } + + .md\:-translate-x-1 { + --transform-translate-x: -0.25rem; + } + + .md\:-translate-x-2 { + --transform-translate-x: -0.5rem; + } + + .md\:-translate-x-3 { + --transform-translate-x: -0.75rem; + } + + .md\:-translate-x-4 { + --transform-translate-x: -1rem; + } + + .md\:-translate-x-5 { + --transform-translate-x: -1.25rem; + } + + .md\:-translate-x-6 { + --transform-translate-x: -1.5rem; + } + + .md\:-translate-x-8 { + --transform-translate-x: -2rem; + } + + .md\:-translate-x-10 { + --transform-translate-x: -2.5rem; + } + + .md\:-translate-x-12 { + --transform-translate-x: -3rem; + } + + .md\:-translate-x-16 { + --transform-translate-x: -4rem; + } + + .md\:-translate-x-20 { + --transform-translate-x: -5rem; + } + + .md\:-translate-x-24 { + --transform-translate-x: -6rem; + } + + .md\:-translate-x-32 { + --transform-translate-x: -8rem; + } + + .md\:-translate-x-40 { + --transform-translate-x: -10rem; + } + + .md\:-translate-x-48 { + --transform-translate-x: -12rem; + } + + .md\:-translate-x-56 { + --transform-translate-x: -14rem; + } + + .md\:-translate-x-64 { + --transform-translate-x: -16rem; + } + + .md\:-translate-x-px { + --transform-translate-x: -1px; + } + + .md\:-translate-x-full { + --transform-translate-x: -100%; + } + + .md\:-translate-x-1\/2 { + --transform-translate-x: -50%; + } + + .md\:translate-x-1\/2 { + --transform-translate-x: 50%; + } + + .md\:translate-x-full { + --transform-translate-x: 100%; + } + + .md\:translate-y-0 { + --transform-translate-y: 0; + } + + .md\:translate-y-1 { + --transform-translate-y: 0.25rem; + } + + .md\:translate-y-2 { + --transform-translate-y: 0.5rem; + } + + .md\:translate-y-3 { + --transform-translate-y: 0.75rem; + } + + .md\:translate-y-4 { + --transform-translate-y: 1rem; + } + + .md\:translate-y-5 { + --transform-translate-y: 1.25rem; + } + + .md\:translate-y-6 { + --transform-translate-y: 1.5rem; + } + + .md\:translate-y-8 { + --transform-translate-y: 2rem; + } + + .md\:translate-y-10 { + --transform-translate-y: 2.5rem; + } + + .md\:translate-y-12 { + --transform-translate-y: 3rem; + } + + .md\:translate-y-16 { + --transform-translate-y: 4rem; + } + + .md\:translate-y-20 { + --transform-translate-y: 5rem; + } + + .md\:translate-y-24 { + --transform-translate-y: 6rem; + } + + .md\:translate-y-32 { + --transform-translate-y: 8rem; + } + + .md\:translate-y-40 { + --transform-translate-y: 10rem; + } + + .md\:translate-y-48 { + --transform-translate-y: 12rem; + } + + .md\:translate-y-56 { + --transform-translate-y: 14rem; + } + + .md\:translate-y-64 { + --transform-translate-y: 16rem; + } + + .md\:translate-y-px { + --transform-translate-y: 1px; + } + + .md\:-translate-y-1 { + --transform-translate-y: -0.25rem; + } + + .md\:-translate-y-2 { + --transform-translate-y: -0.5rem; + } + + .md\:-translate-y-3 { + --transform-translate-y: -0.75rem; + } + + .md\:-translate-y-4 { + --transform-translate-y: -1rem; + } + + .md\:-translate-y-5 { + --transform-translate-y: -1.25rem; + } + + .md\:-translate-y-6 { + --transform-translate-y: -1.5rem; + } + + .md\:-translate-y-8 { + --transform-translate-y: -2rem; + } + + .md\:-translate-y-10 { + --transform-translate-y: -2.5rem; + } + + .md\:-translate-y-12 { + --transform-translate-y: -3rem; + } + + .md\:-translate-y-16 { + --transform-translate-y: -4rem; + } + + .md\:-translate-y-20 { + --transform-translate-y: -5rem; + } + + .md\:-translate-y-24 { + --transform-translate-y: -6rem; + } + + .md\:-translate-y-32 { + --transform-translate-y: -8rem; + } + + .md\:-translate-y-40 { + --transform-translate-y: -10rem; + } + + .md\:-translate-y-48 { + --transform-translate-y: -12rem; + } + + .md\:-translate-y-56 { + --transform-translate-y: -14rem; + } + + .md\:-translate-y-64 { + --transform-translate-y: -16rem; + } + + .md\:-translate-y-px { + --transform-translate-y: -1px; + } + + .md\:-translate-y-full { + --transform-translate-y: -100%; + } + + .md\:-translate-y-1\/2 { + --transform-translate-y: -50%; + } + + .md\:translate-y-1\/2 { + --transform-translate-y: 50%; + } + + .md\:translate-y-full { + --transform-translate-y: 100%; + } + + .md\:hover\:translate-x-0:hover { + --transform-translate-x: 0; + } + + .md\:hover\:translate-x-1:hover { + --transform-translate-x: 0.25rem; + } + + .md\:hover\:translate-x-2:hover { + --transform-translate-x: 0.5rem; + } + + .md\:hover\:translate-x-3:hover { + --transform-translate-x: 0.75rem; + } + + .md\:hover\:translate-x-4:hover { + --transform-translate-x: 1rem; + } + + .md\:hover\:translate-x-5:hover { + --transform-translate-x: 1.25rem; + } + + .md\:hover\:translate-x-6:hover { + --transform-translate-x: 1.5rem; + } + + .md\:hover\:translate-x-8:hover { + --transform-translate-x: 2rem; + } + + .md\:hover\:translate-x-10:hover { + --transform-translate-x: 2.5rem; + } + + .md\:hover\:translate-x-12:hover { + --transform-translate-x: 3rem; + } + + .md\:hover\:translate-x-16:hover { + --transform-translate-x: 4rem; + } + + .md\:hover\:translate-x-20:hover { + --transform-translate-x: 5rem; + } + + .md\:hover\:translate-x-24:hover { + --transform-translate-x: 6rem; + } + + .md\:hover\:translate-x-32:hover { + --transform-translate-x: 8rem; + } + + .md\:hover\:translate-x-40:hover { + --transform-translate-x: 10rem; + } + + .md\:hover\:translate-x-48:hover { + --transform-translate-x: 12rem; + } + + .md\:hover\:translate-x-56:hover { + --transform-translate-x: 14rem; + } + + .md\:hover\:translate-x-64:hover { + --transform-translate-x: 16rem; + } + + .md\:hover\:translate-x-px:hover { + --transform-translate-x: 1px; + } + + .md\:hover\:-translate-x-1:hover { + --transform-translate-x: -0.25rem; + } + + .md\:hover\:-translate-x-2:hover { + --transform-translate-x: -0.5rem; + } + + .md\:hover\:-translate-x-3:hover { + --transform-translate-x: -0.75rem; + } + + .md\:hover\:-translate-x-4:hover { + --transform-translate-x: -1rem; + } + + .md\:hover\:-translate-x-5:hover { + --transform-translate-x: -1.25rem; + } + + .md\:hover\:-translate-x-6:hover { + --transform-translate-x: -1.5rem; + } + + .md\:hover\:-translate-x-8:hover { + --transform-translate-x: -2rem; + } + + .md\:hover\:-translate-x-10:hover { + --transform-translate-x: -2.5rem; + } + + .md\:hover\:-translate-x-12:hover { + --transform-translate-x: -3rem; + } + + .md\:hover\:-translate-x-16:hover { + --transform-translate-x: -4rem; + } + + .md\:hover\:-translate-x-20:hover { + --transform-translate-x: -5rem; + } + + .md\:hover\:-translate-x-24:hover { + --transform-translate-x: -6rem; + } + + .md\:hover\:-translate-x-32:hover { + --transform-translate-x: -8rem; + } + + .md\:hover\:-translate-x-40:hover { + --transform-translate-x: -10rem; + } + + .md\:hover\:-translate-x-48:hover { + --transform-translate-x: -12rem; + } + + .md\:hover\:-translate-x-56:hover { + --transform-translate-x: -14rem; + } + + .md\:hover\:-translate-x-64:hover { + --transform-translate-x: -16rem; + } + + .md\:hover\:-translate-x-px:hover { + --transform-translate-x: -1px; + } + + .md\:hover\:-translate-x-full:hover { + --transform-translate-x: -100%; + } + + .md\:hover\:-translate-x-1\/2:hover { + --transform-translate-x: -50%; + } + + .md\:hover\:translate-x-1\/2:hover { + --transform-translate-x: 50%; + } + + .md\:hover\:translate-x-full:hover { + --transform-translate-x: 100%; + } + + .md\:hover\:translate-y-0:hover { + --transform-translate-y: 0; + } + + .md\:hover\:translate-y-1:hover { + --transform-translate-y: 0.25rem; + } + + .md\:hover\:translate-y-2:hover { + --transform-translate-y: 0.5rem; + } + + .md\:hover\:translate-y-3:hover { + --transform-translate-y: 0.75rem; + } + + .md\:hover\:translate-y-4:hover { + --transform-translate-y: 1rem; + } + + .md\:hover\:translate-y-5:hover { + --transform-translate-y: 1.25rem; + } + + .md\:hover\:translate-y-6:hover { + --transform-translate-y: 1.5rem; + } + + .md\:hover\:translate-y-8:hover { + --transform-translate-y: 2rem; + } + + .md\:hover\:translate-y-10:hover { + --transform-translate-y: 2.5rem; + } + + .md\:hover\:translate-y-12:hover { + --transform-translate-y: 3rem; + } + + .md\:hover\:translate-y-16:hover { + --transform-translate-y: 4rem; + } + + .md\:hover\:translate-y-20:hover { + --transform-translate-y: 5rem; + } + + .md\:hover\:translate-y-24:hover { + --transform-translate-y: 6rem; + } + + .md\:hover\:translate-y-32:hover { + --transform-translate-y: 8rem; + } + + .md\:hover\:translate-y-40:hover { + --transform-translate-y: 10rem; + } + + .md\:hover\:translate-y-48:hover { + --transform-translate-y: 12rem; + } + + .md\:hover\:translate-y-56:hover { + --transform-translate-y: 14rem; + } + + .md\:hover\:translate-y-64:hover { + --transform-translate-y: 16rem; + } + + .md\:hover\:translate-y-px:hover { + --transform-translate-y: 1px; + } + + .md\:hover\:-translate-y-1:hover { + --transform-translate-y: -0.25rem; + } + + .md\:hover\:-translate-y-2:hover { + --transform-translate-y: -0.5rem; + } + + .md\:hover\:-translate-y-3:hover { + --transform-translate-y: -0.75rem; + } + + .md\:hover\:-translate-y-4:hover { + --transform-translate-y: -1rem; + } + + .md\:hover\:-translate-y-5:hover { + --transform-translate-y: -1.25rem; + } + + .md\:hover\:-translate-y-6:hover { + --transform-translate-y: -1.5rem; + } + + .md\:hover\:-translate-y-8:hover { + --transform-translate-y: -2rem; + } + + .md\:hover\:-translate-y-10:hover { + --transform-translate-y: -2.5rem; + } + + .md\:hover\:-translate-y-12:hover { + --transform-translate-y: -3rem; + } + + .md\:hover\:-translate-y-16:hover { + --transform-translate-y: -4rem; + } + + .md\:hover\:-translate-y-20:hover { + --transform-translate-y: -5rem; + } + + .md\:hover\:-translate-y-24:hover { + --transform-translate-y: -6rem; + } + + .md\:hover\:-translate-y-32:hover { + --transform-translate-y: -8rem; + } + + .md\:hover\:-translate-y-40:hover { + --transform-translate-y: -10rem; + } + + .md\:hover\:-translate-y-48:hover { + --transform-translate-y: -12rem; + } + + .md\:hover\:-translate-y-56:hover { + --transform-translate-y: -14rem; + } + + .md\:hover\:-translate-y-64:hover { + --transform-translate-y: -16rem; + } + + .md\:hover\:-translate-y-px:hover { + --transform-translate-y: -1px; + } + + .md\:hover\:-translate-y-full:hover { + --transform-translate-y: -100%; + } + + .md\:hover\:-translate-y-1\/2:hover { + --transform-translate-y: -50%; + } + + .md\:hover\:translate-y-1\/2:hover { + --transform-translate-y: 50%; + } + + .md\:hover\:translate-y-full:hover { + --transform-translate-y: 100%; + } + + .md\:focus\:translate-x-0:focus { + --transform-translate-x: 0; + } + + .md\:focus\:translate-x-1:focus { + --transform-translate-x: 0.25rem; + } + + .md\:focus\:translate-x-2:focus { + --transform-translate-x: 0.5rem; + } + + .md\:focus\:translate-x-3:focus { + --transform-translate-x: 0.75rem; + } + + .md\:focus\:translate-x-4:focus { + --transform-translate-x: 1rem; + } + + .md\:focus\:translate-x-5:focus { + --transform-translate-x: 1.25rem; + } + + .md\:focus\:translate-x-6:focus { + --transform-translate-x: 1.5rem; + } + + .md\:focus\:translate-x-8:focus { + --transform-translate-x: 2rem; + } + + .md\:focus\:translate-x-10:focus { + --transform-translate-x: 2.5rem; + } + + .md\:focus\:translate-x-12:focus { + --transform-translate-x: 3rem; + } + + .md\:focus\:translate-x-16:focus { + --transform-translate-x: 4rem; + } + + .md\:focus\:translate-x-20:focus { + --transform-translate-x: 5rem; + } + + .md\:focus\:translate-x-24:focus { + --transform-translate-x: 6rem; + } + + .md\:focus\:translate-x-32:focus { + --transform-translate-x: 8rem; + } + + .md\:focus\:translate-x-40:focus { + --transform-translate-x: 10rem; + } + + .md\:focus\:translate-x-48:focus { + --transform-translate-x: 12rem; + } + + .md\:focus\:translate-x-56:focus { + --transform-translate-x: 14rem; + } + + .md\:focus\:translate-x-64:focus { + --transform-translate-x: 16rem; + } + + .md\:focus\:translate-x-px:focus { + --transform-translate-x: 1px; + } + + .md\:focus\:-translate-x-1:focus { + --transform-translate-x: -0.25rem; + } + + .md\:focus\:-translate-x-2:focus { + --transform-translate-x: -0.5rem; + } + + .md\:focus\:-translate-x-3:focus { + --transform-translate-x: -0.75rem; + } + + .md\:focus\:-translate-x-4:focus { + --transform-translate-x: -1rem; + } + + .md\:focus\:-translate-x-5:focus { + --transform-translate-x: -1.25rem; + } + + .md\:focus\:-translate-x-6:focus { + --transform-translate-x: -1.5rem; + } + + .md\:focus\:-translate-x-8:focus { + --transform-translate-x: -2rem; + } + + .md\:focus\:-translate-x-10:focus { + --transform-translate-x: -2.5rem; + } + + .md\:focus\:-translate-x-12:focus { + --transform-translate-x: -3rem; + } + + .md\:focus\:-translate-x-16:focus { + --transform-translate-x: -4rem; + } + + .md\:focus\:-translate-x-20:focus { + --transform-translate-x: -5rem; + } + + .md\:focus\:-translate-x-24:focus { + --transform-translate-x: -6rem; + } + + .md\:focus\:-translate-x-32:focus { + --transform-translate-x: -8rem; + } + + .md\:focus\:-translate-x-40:focus { + --transform-translate-x: -10rem; + } + + .md\:focus\:-translate-x-48:focus { + --transform-translate-x: -12rem; + } + + .md\:focus\:-translate-x-56:focus { + --transform-translate-x: -14rem; + } + + .md\:focus\:-translate-x-64:focus { + --transform-translate-x: -16rem; + } + + .md\:focus\:-translate-x-px:focus { + --transform-translate-x: -1px; + } + + .md\:focus\:-translate-x-full:focus { + --transform-translate-x: -100%; + } + + .md\:focus\:-translate-x-1\/2:focus { + --transform-translate-x: -50%; + } + + .md\:focus\:translate-x-1\/2:focus { + --transform-translate-x: 50%; + } + + .md\:focus\:translate-x-full:focus { + --transform-translate-x: 100%; + } + + .md\:focus\:translate-y-0:focus { + --transform-translate-y: 0; + } + + .md\:focus\:translate-y-1:focus { + --transform-translate-y: 0.25rem; + } + + .md\:focus\:translate-y-2:focus { + --transform-translate-y: 0.5rem; + } + + .md\:focus\:translate-y-3:focus { + --transform-translate-y: 0.75rem; + } + + .md\:focus\:translate-y-4:focus { + --transform-translate-y: 1rem; + } + + .md\:focus\:translate-y-5:focus { + --transform-translate-y: 1.25rem; + } + + .md\:focus\:translate-y-6:focus { + --transform-translate-y: 1.5rem; + } + + .md\:focus\:translate-y-8:focus { + --transform-translate-y: 2rem; + } + + .md\:focus\:translate-y-10:focus { + --transform-translate-y: 2.5rem; + } + + .md\:focus\:translate-y-12:focus { + --transform-translate-y: 3rem; + } + + .md\:focus\:translate-y-16:focus { + --transform-translate-y: 4rem; + } + + .md\:focus\:translate-y-20:focus { + --transform-translate-y: 5rem; + } + + .md\:focus\:translate-y-24:focus { + --transform-translate-y: 6rem; + } + + .md\:focus\:translate-y-32:focus { + --transform-translate-y: 8rem; + } + + .md\:focus\:translate-y-40:focus { + --transform-translate-y: 10rem; + } + + .md\:focus\:translate-y-48:focus { + --transform-translate-y: 12rem; + } + + .md\:focus\:translate-y-56:focus { + --transform-translate-y: 14rem; + } + + .md\:focus\:translate-y-64:focus { + --transform-translate-y: 16rem; + } + + .md\:focus\:translate-y-px:focus { + --transform-translate-y: 1px; + } + + .md\:focus\:-translate-y-1:focus { + --transform-translate-y: -0.25rem; + } + + .md\:focus\:-translate-y-2:focus { + --transform-translate-y: -0.5rem; + } + + .md\:focus\:-translate-y-3:focus { + --transform-translate-y: -0.75rem; + } + + .md\:focus\:-translate-y-4:focus { + --transform-translate-y: -1rem; + } + + .md\:focus\:-translate-y-5:focus { + --transform-translate-y: -1.25rem; + } + + .md\:focus\:-translate-y-6:focus { + --transform-translate-y: -1.5rem; + } + + .md\:focus\:-translate-y-8:focus { + --transform-translate-y: -2rem; + } + + .md\:focus\:-translate-y-10:focus { + --transform-translate-y: -2.5rem; + } + + .md\:focus\:-translate-y-12:focus { + --transform-translate-y: -3rem; + } + + .md\:focus\:-translate-y-16:focus { + --transform-translate-y: -4rem; + } + + .md\:focus\:-translate-y-20:focus { + --transform-translate-y: -5rem; + } + + .md\:focus\:-translate-y-24:focus { + --transform-translate-y: -6rem; + } + + .md\:focus\:-translate-y-32:focus { + --transform-translate-y: -8rem; + } + + .md\:focus\:-translate-y-40:focus { + --transform-translate-y: -10rem; + } + + .md\:focus\:-translate-y-48:focus { + --transform-translate-y: -12rem; + } + + .md\:focus\:-translate-y-56:focus { + --transform-translate-y: -14rem; + } + + .md\:focus\:-translate-y-64:focus { + --transform-translate-y: -16rem; + } + + .md\:focus\:-translate-y-px:focus { + --transform-translate-y: -1px; + } + + .md\:focus\:-translate-y-full:focus { + --transform-translate-y: -100%; + } + + .md\:focus\:-translate-y-1\/2:focus { + --transform-translate-y: -50%; + } + + .md\:focus\:translate-y-1\/2:focus { + --transform-translate-y: 50%; + } + + .md\:focus\:translate-y-full:focus { + --transform-translate-y: 100%; + } + + .md\:skew-x-0 { + --transform-skew-x: 0; + } + + .md\:skew-x-3 { + --transform-skew-x: 3deg; + } + + .md\:skew-x-6 { + --transform-skew-x: 6deg; + } + + .md\:skew-x-12 { + --transform-skew-x: 12deg; + } + + .md\:-skew-x-12 { + --transform-skew-x: -12deg; + } + + .md\:-skew-x-6 { + --transform-skew-x: -6deg; + } + + .md\:-skew-x-3 { + --transform-skew-x: -3deg; + } + + .md\:skew-y-0 { + --transform-skew-y: 0; + } + + .md\:skew-y-3 { + --transform-skew-y: 3deg; + } + + .md\:skew-y-6 { + --transform-skew-y: 6deg; + } + + .md\:skew-y-12 { + --transform-skew-y: 12deg; + } + + .md\:-skew-y-12 { + --transform-skew-y: -12deg; + } + + .md\:-skew-y-6 { + --transform-skew-y: -6deg; + } + + .md\:-skew-y-3 { + --transform-skew-y: -3deg; + } + + .md\:hover\:skew-x-0:hover { + --transform-skew-x: 0; + } + + .md\:hover\:skew-x-3:hover { + --transform-skew-x: 3deg; + } + + .md\:hover\:skew-x-6:hover { + --transform-skew-x: 6deg; + } + + .md\:hover\:skew-x-12:hover { + --transform-skew-x: 12deg; + } + + .md\:hover\:-skew-x-12:hover { + --transform-skew-x: -12deg; + } + + .md\:hover\:-skew-x-6:hover { + --transform-skew-x: -6deg; + } + + .md\:hover\:-skew-x-3:hover { + --transform-skew-x: -3deg; + } + + .md\:hover\:skew-y-0:hover { + --transform-skew-y: 0; + } + + .md\:hover\:skew-y-3:hover { + --transform-skew-y: 3deg; + } + + .md\:hover\:skew-y-6:hover { + --transform-skew-y: 6deg; + } + + .md\:hover\:skew-y-12:hover { + --transform-skew-y: 12deg; + } + + .md\:hover\:-skew-y-12:hover { + --transform-skew-y: -12deg; + } + + .md\:hover\:-skew-y-6:hover { + --transform-skew-y: -6deg; + } + + .md\:hover\:-skew-y-3:hover { + --transform-skew-y: -3deg; + } + + .md\:focus\:skew-x-0:focus { + --transform-skew-x: 0; + } + + .md\:focus\:skew-x-3:focus { + --transform-skew-x: 3deg; + } + + .md\:focus\:skew-x-6:focus { + --transform-skew-x: 6deg; + } + + .md\:focus\:skew-x-12:focus { + --transform-skew-x: 12deg; + } + + .md\:focus\:-skew-x-12:focus { + --transform-skew-x: -12deg; + } + + .md\:focus\:-skew-x-6:focus { + --transform-skew-x: -6deg; + } + + .md\:focus\:-skew-x-3:focus { + --transform-skew-x: -3deg; + } + + .md\:focus\:skew-y-0:focus { + --transform-skew-y: 0; + } + + .md\:focus\:skew-y-3:focus { + --transform-skew-y: 3deg; + } + + .md\:focus\:skew-y-6:focus { + --transform-skew-y: 6deg; + } + + .md\:focus\:skew-y-12:focus { + --transform-skew-y: 12deg; + } + + .md\:focus\:-skew-y-12:focus { + --transform-skew-y: -12deg; + } + + .md\:focus\:-skew-y-6:focus { + --transform-skew-y: -6deg; + } + + .md\:focus\:-skew-y-3:focus { + --transform-skew-y: -3deg; + } + + .md\:transition-none { + transition-property: none; + } + + .md\:transition-all { + transition-property: all; + } + + .md\:transition { + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; + } + + .md\:transition-colors { + transition-property: background-color, border-color, color, fill, stroke; + } + + .md\:transition-opacity { + transition-property: opacity; + } + + .md\:transition-shadow { + transition-property: box-shadow; + } + + .md\:transition-transform { + transition-property: transform; + } + + .md\:ease-linear { + transition-timing-function: linear; + } + + .md\:ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); + } + + .md\:ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + + .md\:ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .md\:duration-75 { + transition-duration: 75ms; + } + + .md\:duration-100 { + transition-duration: 100ms; + } + + .md\:duration-150 { + transition-duration: 150ms; + } + + .md\:duration-200 { + transition-duration: 200ms; + } + + .md\:duration-300 { + transition-duration: 300ms; + } + + .md\:duration-500 { + transition-duration: 500ms; + } + + .md\:duration-700 { + transition-duration: 700ms; + } + + .md\:duration-1000 { + transition-duration: 1000ms; + } + + .md\:delay-75 { + transition-delay: 75ms; + } + + .md\:delay-100 { + transition-delay: 100ms; + } + + .md\:delay-150 { + transition-delay: 150ms; + } + + .md\:delay-200 { + transition-delay: 200ms; + } + + .md\:delay-300 { + transition-delay: 300ms; + } + + .md\:delay-500 { + transition-delay: 500ms; + } + + .md\:delay-700 { + transition-delay: 700ms; + } + + .md\:delay-1000 { + transition-delay: 1000ms; + } + + .md\:animate-none { + -webkit-animation: none; + animation: none; + } + + .md\:animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + } + + .md\:animate-ping { + -webkit-animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + } + + .md\:animate-pulse { + -webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + .md\:animate-bounce { + -webkit-animation: bounce 1s infinite; + animation: bounce 1s infinite; + } +} + +@media (min-width: 1024px) { + .lg\:container { + width: 100%; + } + + @media (min-width: 640px) { + .lg\:container { + max-width: 640px; + } + } + + @media (min-width: 768px) { + .lg\:container { + max-width: 768px; + } + } + + @media (min-width: 1024px) { + .lg\:container { + max-width: 1024px; + } + } + + @media (min-width: 1280px) { + .lg\:container { + max-width: 1280px; + } + } + + .lg\:space-y-0 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0px * var(--space-y-reverse)); + } + + .lg\:space-x-0 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0px * var(--space-x-reverse)); + margin-left: calc(0px * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.25rem * var(--space-y-reverse)); + } + + .lg\:space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.25rem * var(--space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.5rem * var(--space-y-reverse)); + } + + .lg\:space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.5rem * var(--space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.75rem * var(--space-y-reverse)); + } + + .lg\:space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.75rem * var(--space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1rem * var(--space-y-reverse)); + } + + .lg\:space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1rem * var(--space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.25rem * var(--space-y-reverse)); + } + + .lg\:space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.25rem * var(--space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.5rem * var(--space-y-reverse)); + } + + .lg\:space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.5rem * var(--space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2rem * var(--space-y-reverse)); + } + + .lg\:space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2rem * var(--space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2.5rem * var(--space-y-reverse)); + } + + .lg\:space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2.5rem * var(--space-x-reverse)); + margin-left: calc(2.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(3rem * var(--space-y-reverse)); + } + + .lg\:space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(3rem * var(--space-x-reverse)); + margin-left: calc(3rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(4rem * var(--space-y-reverse)); + } + + .lg\:space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(4rem * var(--space-x-reverse)); + margin-left: calc(4rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(5rem * var(--space-y-reverse)); + } + + .lg\:space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(5rem * var(--space-x-reverse)); + margin-left: calc(5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(6rem * var(--space-y-reverse)); + } + + .lg\:space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(6rem * var(--space-x-reverse)); + margin-left: calc(6rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(8rem * var(--space-y-reverse)); + } + + .lg\:space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(8rem * var(--space-x-reverse)); + margin-left: calc(8rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(10rem * var(--space-y-reverse)); + } + + .lg\:space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(10rem * var(--space-x-reverse)); + margin-left: calc(10rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(12rem * var(--space-y-reverse)); + } + + .lg\:space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(12rem * var(--space-x-reverse)); + margin-left: calc(12rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(14rem * var(--space-y-reverse)); + } + + .lg\:space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(14rem * var(--space-x-reverse)); + margin-left: calc(14rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(16rem * var(--space-y-reverse)); + } + + .lg\:space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(16rem * var(--space-x-reverse)); + margin-left: calc(16rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1px * var(--space-y-reverse)); + } + + .lg\:space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1px * var(--space-x-reverse)); + margin-left: calc(1px * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.25rem * var(--space-y-reverse)); + } + + .lg\:-space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.25rem * var(--space-x-reverse)); + margin-left: calc(-0.25rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.5rem * var(--space-y-reverse)); + } + + .lg\:-space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.5rem * var(--space-x-reverse)); + margin-left: calc(-0.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.75rem * var(--space-y-reverse)); + } + + .lg\:-space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.75rem * var(--space-x-reverse)); + margin-left: calc(-0.75rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1rem * var(--space-y-reverse)); + } + + .lg\:-space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1rem * var(--space-x-reverse)); + margin-left: calc(-1rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.25rem * var(--space-y-reverse)); + } + + .lg\:-space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.25rem * var(--space-x-reverse)); + margin-left: calc(-1.25rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.5rem * var(--space-y-reverse)); + } + + .lg\:-space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.5rem * var(--space-x-reverse)); + margin-left: calc(-1.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2rem * var(--space-y-reverse)); + } + + .lg\:-space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2rem * var(--space-x-reverse)); + margin-left: calc(-2rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2.5rem * var(--space-y-reverse)); + } + + .lg\:-space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2.5rem * var(--space-x-reverse)); + margin-left: calc(-2.5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-3rem * var(--space-y-reverse)); + } + + .lg\:-space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-3rem * var(--space-x-reverse)); + margin-left: calc(-3rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-4rem * var(--space-y-reverse)); + } + + .lg\:-space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-4rem * var(--space-x-reverse)); + margin-left: calc(-4rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-5rem * var(--space-y-reverse)); + } + + .lg\:-space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-5rem * var(--space-x-reverse)); + margin-left: calc(-5rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-6rem * var(--space-y-reverse)); + } + + .lg\:-space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-6rem * var(--space-x-reverse)); + margin-left: calc(-6rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-8rem * var(--space-y-reverse)); + } + + .lg\:-space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-8rem * var(--space-x-reverse)); + margin-left: calc(-8rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-10rem * var(--space-y-reverse)); + } + + .lg\:-space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-10rem * var(--space-x-reverse)); + margin-left: calc(-10rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-12rem * var(--space-y-reverse)); + } + + .lg\:-space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-12rem * var(--space-x-reverse)); + margin-left: calc(-12rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-14rem * var(--space-y-reverse)); + } + + .lg\:-space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-14rem * var(--space-x-reverse)); + margin-left: calc(-14rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-16rem * var(--space-y-reverse)); + } + + .lg\:-space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-16rem * var(--space-x-reverse)); + margin-left: calc(-16rem * calc(1 - var(--space-x-reverse))); + } + + .lg\:-space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1px * var(--space-y-reverse)); + } + + .lg\:-space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1px * var(--space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--space-x-reverse))); + } + + .lg\:space-y-reverse > :not(template) ~ :not(template) { + --space-y-reverse: 1; + } + + .lg\:space-x-reverse > :not(template) ~ :not(template) { + --space-x-reverse: 1; + } + + .lg\:divide-y-0 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(0px * var(--divide-y-reverse)); + } + + .lg\:divide-x-0 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(0px * var(--divide-x-reverse)); + border-left-width: calc(0px * calc(1 - var(--divide-x-reverse))); + } + + .lg\:divide-y-2 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(2px * var(--divide-y-reverse)); + } + + .lg\:divide-x-2 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(2px * var(--divide-x-reverse)); + border-left-width: calc(2px * calc(1 - var(--divide-x-reverse))); + } + + .lg\:divide-y-4 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(4px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(4px * var(--divide-y-reverse)); + } + + .lg\:divide-x-4 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(4px * var(--divide-x-reverse)); + border-left-width: calc(4px * calc(1 - var(--divide-x-reverse))); + } + + .lg\:divide-y-8 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(8px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(8px * var(--divide-y-reverse)); + } + + .lg\:divide-x-8 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(8px * var(--divide-x-reverse)); + border-left-width: calc(8px * calc(1 - var(--divide-x-reverse))); + } + + .lg\:divide-y > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(1px * var(--divide-y-reverse)); + } + + .lg\:divide-x > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(1px * var(--divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--divide-x-reverse))); + } + + .lg\:divide-y-reverse > :not(template) ~ :not(template) { + --divide-y-reverse: 1; + } + + .lg\:divide-x-reverse > :not(template) ~ :not(template) { + --divide-x-reverse: 1; + } + + .lg\:divide-transparent > :not(template) ~ :not(template) { + border-color: transparent; + } + + .lg\:divide-current > :not(template) ~ :not(template) { + border-color: currentColor; + } + + .lg\:divide-black > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--divide-opacity)); + } + + .lg\:divide-white > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--divide-opacity)); + } + + .lg\:divide-gray-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--divide-opacity)); + } + + .lg\:divide-gray-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--divide-opacity)); + } + + .lg\:divide-gray-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--divide-opacity)); + } + + .lg\:divide-gray-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--divide-opacity)); + } + + .lg\:divide-gray-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--divide-opacity)); + } + + .lg\:divide-gray-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--divide-opacity)); + } + + .lg\:divide-gray-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--divide-opacity)); + } + + .lg\:divide-gray-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--divide-opacity)); + } + + .lg\:divide-gray-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--divide-opacity)); + } + + .lg\:divide-red-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--divide-opacity)); + } + + .lg\:divide-red-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--divide-opacity)); + } + + .lg\:divide-red-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--divide-opacity)); + } + + .lg\:divide-red-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--divide-opacity)); + } + + .lg\:divide-red-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--divide-opacity)); + } + + .lg\:divide-red-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--divide-opacity)); + } + + .lg\:divide-red-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--divide-opacity)); + } + + .lg\:divide-red-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--divide-opacity)); + } + + .lg\:divide-red-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--divide-opacity)); + } + + .lg\:divide-orange-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--divide-opacity)); + } + + .lg\:divide-orange-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--divide-opacity)); + } + + .lg\:divide-orange-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--divide-opacity)); + } + + .lg\:divide-orange-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--divide-opacity)); + } + + .lg\:divide-orange-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--divide-opacity)); + } + + .lg\:divide-orange-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--divide-opacity)); + } + + .lg\:divide-orange-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--divide-opacity)); + } + + .lg\:divide-orange-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--divide-opacity)); + } + + .lg\:divide-orange-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--divide-opacity)); + } + + .lg\:divide-yellow-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--divide-opacity)); + } + + .lg\:divide-yellow-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--divide-opacity)); + } + + .lg\:divide-yellow-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--divide-opacity)); + } + + .lg\:divide-yellow-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--divide-opacity)); + } + + .lg\:divide-yellow-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--divide-opacity)); + } + + .lg\:divide-yellow-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--divide-opacity)); + } + + .lg\:divide-yellow-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--divide-opacity)); + } + + .lg\:divide-yellow-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--divide-opacity)); + } + + .lg\:divide-yellow-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--divide-opacity)); + } + + .lg\:divide-green-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--divide-opacity)); + } + + .lg\:divide-green-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--divide-opacity)); + } + + .lg\:divide-green-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--divide-opacity)); + } + + .lg\:divide-green-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--divide-opacity)); + } + + .lg\:divide-green-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--divide-opacity)); + } + + .lg\:divide-green-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--divide-opacity)); + } + + .lg\:divide-green-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--divide-opacity)); + } + + .lg\:divide-green-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--divide-opacity)); + } + + .lg\:divide-green-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--divide-opacity)); + } + + .lg\:divide-teal-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--divide-opacity)); + } + + .lg\:divide-teal-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--divide-opacity)); + } + + .lg\:divide-teal-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--divide-opacity)); + } + + .lg\:divide-teal-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--divide-opacity)); + } + + .lg\:divide-teal-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--divide-opacity)); + } + + .lg\:divide-teal-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--divide-opacity)); + } + + .lg\:divide-teal-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--divide-opacity)); + } + + .lg\:divide-teal-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--divide-opacity)); + } + + .lg\:divide-teal-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--divide-opacity)); + } + + .lg\:divide-blue-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--divide-opacity)); + } + + .lg\:divide-blue-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--divide-opacity)); + } + + .lg\:divide-blue-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--divide-opacity)); + } + + .lg\:divide-blue-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--divide-opacity)); + } + + .lg\:divide-blue-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--divide-opacity)); + } + + .lg\:divide-blue-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--divide-opacity)); + } + + .lg\:divide-blue-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--divide-opacity)); + } + + .lg\:divide-blue-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--divide-opacity)); + } + + .lg\:divide-blue-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--divide-opacity)); + } + + .lg\:divide-indigo-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--divide-opacity)); + } + + .lg\:divide-indigo-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--divide-opacity)); + } + + .lg\:divide-indigo-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--divide-opacity)); + } + + .lg\:divide-indigo-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--divide-opacity)); + } + + .lg\:divide-indigo-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--divide-opacity)); + } + + .lg\:divide-indigo-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--divide-opacity)); + } + + .lg\:divide-indigo-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--divide-opacity)); + } + + .lg\:divide-indigo-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--divide-opacity)); + } + + .lg\:divide-indigo-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--divide-opacity)); + } + + .lg\:divide-purple-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--divide-opacity)); + } + + .lg\:divide-purple-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--divide-opacity)); + } + + .lg\:divide-purple-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--divide-opacity)); + } + + .lg\:divide-purple-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--divide-opacity)); + } + + .lg\:divide-purple-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--divide-opacity)); + } + + .lg\:divide-purple-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--divide-opacity)); + } + + .lg\:divide-purple-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--divide-opacity)); + } + + .lg\:divide-purple-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--divide-opacity)); + } + + .lg\:divide-purple-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--divide-opacity)); + } + + .lg\:divide-pink-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--divide-opacity)); + } + + .lg\:divide-pink-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--divide-opacity)); + } + + .lg\:divide-pink-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--divide-opacity)); + } + + .lg\:divide-pink-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--divide-opacity)); + } + + .lg\:divide-pink-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--divide-opacity)); + } + + .lg\:divide-pink-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--divide-opacity)); + } + + .lg\:divide-pink-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--divide-opacity)); + } + + .lg\:divide-pink-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--divide-opacity)); + } + + .lg\:divide-pink-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--divide-opacity)); + } + + .lg\:divide-solid > :not(template) ~ :not(template) { + border-style: solid; + } + + .lg\:divide-dashed > :not(template) ~ :not(template) { + border-style: dashed; + } + + .lg\:divide-dotted > :not(template) ~ :not(template) { + border-style: dotted; + } + + .lg\:divide-double > :not(template) ~ :not(template) { + border-style: double; + } + + .lg\:divide-none > :not(template) ~ :not(template) { + border-style: none; + } + + .lg\:divide-opacity-0 > :not(template) ~ :not(template) { + --divide-opacity: 0; + } + + .lg\:divide-opacity-25 > :not(template) ~ :not(template) { + --divide-opacity: 0.25; + } + + .lg\:divide-opacity-50 > :not(template) ~ :not(template) { + --divide-opacity: 0.5; + } + + .lg\:divide-opacity-75 > :not(template) ~ :not(template) { + --divide-opacity: 0.75; + } + + .lg\:divide-opacity-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + } + + .lg\:sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .lg\:not-sr-only { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .lg\:focus\:sr-only:focus { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .lg\:focus\:not-sr-only:focus { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .lg\:appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + .lg\:bg-fixed { + background-attachment: fixed; + } + + .lg\:bg-local { + background-attachment: local; + } + + .lg\:bg-scroll { + background-attachment: scroll; + } + + .lg\:bg-clip-border { + background-clip: border-box; + } + + .lg\:bg-clip-padding { + background-clip: padding-box; + } + + .lg\:bg-clip-content { + background-clip: content-box; + } + + .lg\:bg-clip-text { + -webkit-background-clip: text; + background-clip: text; + } + + .lg\:bg-transparent { + background-color: transparent; + } + + .lg\:bg-current { + background-color: currentColor; + } + + .lg\:bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .lg\:bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .lg\:bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .lg\:bg-gray-200 { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .lg\:bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .lg\:bg-gray-400 { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .lg\:bg-gray-500 { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .lg\:bg-gray-600 { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .lg\:bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .lg\:bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .lg\:bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .lg\:bg-red-100 { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .lg\:bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .lg\:bg-red-300 { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .lg\:bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .lg\:bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .lg\:bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .lg\:bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .lg\:bg-red-800 { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .lg\:bg-red-900 { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .lg\:bg-orange-100 { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .lg\:bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .lg\:bg-orange-300 { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .lg\:bg-orange-400 { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .lg\:bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .lg\:bg-orange-600 { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .lg\:bg-orange-700 { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .lg\:bg-orange-800 { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .lg\:bg-orange-900 { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .lg\:bg-yellow-100 { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .lg\:bg-yellow-200 { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .lg\:bg-yellow-300 { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .lg\:bg-yellow-400 { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .lg\:bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .lg\:bg-yellow-600 { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .lg\:bg-yellow-700 { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .lg\:bg-yellow-800 { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .lg\:bg-yellow-900 { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .lg\:bg-green-100 { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .lg\:bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .lg\:bg-green-300 { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .lg\:bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .lg\:bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .lg\:bg-green-600 { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .lg\:bg-green-700 { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .lg\:bg-green-800 { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .lg\:bg-green-900 { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .lg\:bg-teal-100 { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .lg\:bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .lg\:bg-teal-300 { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .lg\:bg-teal-400 { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .lg\:bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .lg\:bg-teal-600 { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .lg\:bg-teal-700 { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .lg\:bg-teal-800 { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .lg\:bg-teal-900 { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .lg\:bg-blue-100 { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .lg\:bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .lg\:bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .lg\:bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .lg\:bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .lg\:bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .lg\:bg-blue-700 { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .lg\:bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .lg\:bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .lg\:bg-indigo-100 { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .lg\:bg-indigo-200 { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .lg\:bg-indigo-300 { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .lg\:bg-indigo-400 { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .lg\:bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .lg\:bg-indigo-600 { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .lg\:bg-indigo-700 { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .lg\:bg-indigo-800 { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .lg\:bg-indigo-900 { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .lg\:bg-purple-100 { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .lg\:bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .lg\:bg-purple-300 { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .lg\:bg-purple-400 { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .lg\:bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .lg\:bg-purple-600 { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .lg\:bg-purple-700 { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .lg\:bg-purple-800 { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .lg\:bg-purple-900 { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .lg\:bg-pink-100 { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .lg\:bg-pink-200 { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .lg\:bg-pink-300 { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .lg\:bg-pink-400 { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .lg\:bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .lg\:bg-pink-600 { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .lg\:bg-pink-700 { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .lg\:bg-pink-800 { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .lg\:bg-pink-900 { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .lg\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .lg\:hover\:bg-current:hover { + background-color: currentColor; + } + + .lg\:hover\:bg-black:hover { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .lg\:hover\:bg-white:hover { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-100:hover { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-200:hover { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-300:hover { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-400:hover { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-500:hover { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-600:hover { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-700:hover { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-800:hover { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .lg\:hover\:bg-gray-900:hover { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-100:hover { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-200:hover { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-300:hover { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-400:hover { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-500:hover { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-600:hover { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-700:hover { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-800:hover { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .lg\:hover\:bg-red-900:hover { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-100:hover { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-200:hover { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-300:hover { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-400:hover { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-500:hover { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-600:hover { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-700:hover { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-800:hover { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .lg\:hover\:bg-orange-900:hover { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-100:hover { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-200:hover { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-300:hover { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-400:hover { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-500:hover { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-600:hover { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-700:hover { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-800:hover { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .lg\:hover\:bg-yellow-900:hover { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-100:hover { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-200:hover { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-300:hover { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-400:hover { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-500:hover { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-600:hover { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-700:hover { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-800:hover { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .lg\:hover\:bg-green-900:hover { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-100:hover { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-200:hover { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-300:hover { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-400:hover { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-500:hover { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-600:hover { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-700:hover { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-800:hover { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .lg\:hover\:bg-teal-900:hover { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-100:hover { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-200:hover { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-300:hover { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-400:hover { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-500:hover { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-600:hover { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-700:hover { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-800:hover { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .lg\:hover\:bg-blue-900:hover { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-100:hover { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-200:hover { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-300:hover { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-400:hover { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-500:hover { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-600:hover { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-700:hover { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-800:hover { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .lg\:hover\:bg-indigo-900:hover { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-100:hover { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-200:hover { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-300:hover { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-400:hover { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-500:hover { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-600:hover { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-700:hover { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-800:hover { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .lg\:hover\:bg-purple-900:hover { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-100:hover { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-200:hover { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-300:hover { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-400:hover { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-500:hover { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-600:hover { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-700:hover { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-800:hover { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .lg\:hover\:bg-pink-900:hover { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .lg\:focus\:bg-transparent:focus { + background-color: transparent; + } + + .lg\:focus\:bg-current:focus { + background-color: currentColor; + } + + .lg\:focus\:bg-black:focus { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .lg\:focus\:bg-white:focus { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-100:focus { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-200:focus { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-300:focus { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-400:focus { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-500:focus { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-600:focus { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-700:focus { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-800:focus { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .lg\:focus\:bg-gray-900:focus { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-100:focus { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-200:focus { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-300:focus { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-400:focus { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-500:focus { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-600:focus { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-700:focus { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-800:focus { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .lg\:focus\:bg-red-900:focus { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-100:focus { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-200:focus { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-300:focus { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-400:focus { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-500:focus { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-600:focus { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-700:focus { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-800:focus { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .lg\:focus\:bg-orange-900:focus { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-100:focus { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-200:focus { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-300:focus { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-400:focus { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-500:focus { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-600:focus { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-700:focus { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-800:focus { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .lg\:focus\:bg-yellow-900:focus { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-100:focus { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-200:focus { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-300:focus { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-400:focus { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-500:focus { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-600:focus { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-700:focus { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-800:focus { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .lg\:focus\:bg-green-900:focus { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-100:focus { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-200:focus { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-300:focus { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-400:focus { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-500:focus { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-600:focus { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-700:focus { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-800:focus { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .lg\:focus\:bg-teal-900:focus { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-100:focus { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-200:focus { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-300:focus { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-400:focus { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-500:focus { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-600:focus { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-700:focus { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-800:focus { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .lg\:focus\:bg-blue-900:focus { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-100:focus { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-200:focus { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-300:focus { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-400:focus { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-500:focus { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-600:focus { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-700:focus { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-800:focus { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .lg\:focus\:bg-indigo-900:focus { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-100:focus { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-200:focus { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-300:focus { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-400:focus { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-500:focus { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-600:focus { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-700:focus { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-800:focus { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .lg\:focus\:bg-purple-900:focus { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-100:focus { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-200:focus { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-300:focus { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-400:focus { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-500:focus { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-600:focus { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-700:focus { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-800:focus { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .lg\:focus\:bg-pink-900:focus { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .lg\:bg-none { + background-image: none; + } + + .lg\:bg-gradient-to-t { + background-image: linear-gradient(to top, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-r { + background-image: linear-gradient(to right, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-l { + background-image: linear-gradient(to left, var(--gradient-color-stops)); + } + + .lg\:bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--gradient-color-stops)); + } + + .lg\:from-transparent { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:from-current { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:from-black { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:from-white { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:from-gray-100 { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:from-gray-200 { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:from-gray-300 { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:from-gray-400 { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:from-gray-500 { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:from-gray-600 { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:from-gray-700 { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:from-gray-800 { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:from-gray-900 { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:from-red-100 { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:from-red-200 { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:from-red-300 { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:from-red-400 { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:from-red-500 { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:from-red-600 { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:from-red-700 { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:from-red-800 { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:from-red-900 { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:from-orange-100 { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:from-orange-200 { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:from-orange-300 { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:from-orange-400 { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:from-orange-500 { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:from-orange-600 { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:from-orange-700 { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:from-orange-800 { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:from-orange-900 { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:from-yellow-100 { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:from-yellow-200 { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:from-yellow-300 { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:from-yellow-400 { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:from-yellow-500 { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:from-yellow-600 { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:from-yellow-700 { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:from-yellow-800 { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:from-yellow-900 { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:from-green-100 { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:from-green-200 { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:from-green-300 { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:from-green-400 { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:from-green-500 { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:from-green-600 { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:from-green-700 { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:from-green-800 { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:from-green-900 { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:from-teal-100 { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:from-teal-200 { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:from-teal-300 { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:from-teal-400 { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:from-teal-500 { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:from-teal-600 { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:from-teal-700 { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:from-teal-800 { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:from-teal-900 { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:from-blue-100 { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:from-blue-200 { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:from-blue-300 { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:from-blue-400 { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:from-blue-500 { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:from-blue-600 { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:from-blue-700 { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:from-blue-800 { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:from-blue-900 { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:from-indigo-100 { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:from-indigo-200 { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:from-indigo-300 { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:from-indigo-400 { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:from-indigo-500 { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:from-indigo-600 { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:from-indigo-700 { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:from-indigo-800 { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:from-indigo-900 { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:from-purple-100 { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:from-purple-200 { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:from-purple-300 { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:from-purple-400 { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:from-purple-500 { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:from-purple-600 { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:from-purple-700 { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:from-purple-800 { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:from-purple-900 { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:from-pink-100 { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:from-pink-200 { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:from-pink-300 { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:from-pink-400 { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:from-pink-500 { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:from-pink-600 { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:from-pink-700 { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:from-pink-800 { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:from-pink-900 { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:via-transparent { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:via-current { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:via-black { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:via-white { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:via-gray-100 { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:via-gray-200 { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:via-gray-300 { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:via-gray-400 { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:via-gray-500 { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:via-gray-600 { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:via-gray-700 { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:via-gray-800 { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:via-gray-900 { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:via-red-100 { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:via-red-200 { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:via-red-300 { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:via-red-400 { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:via-red-500 { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:via-red-600 { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:via-red-700 { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:via-red-800 { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:via-red-900 { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:via-orange-100 { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:via-orange-200 { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:via-orange-300 { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:via-orange-400 { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:via-orange-500 { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:via-orange-600 { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:via-orange-700 { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:via-orange-800 { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:via-orange-900 { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:via-yellow-100 { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:via-yellow-200 { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:via-yellow-300 { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:via-yellow-400 { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:via-yellow-500 { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:via-yellow-600 { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:via-yellow-700 { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:via-yellow-800 { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:via-yellow-900 { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:via-green-100 { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:via-green-200 { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:via-green-300 { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:via-green-400 { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:via-green-500 { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:via-green-600 { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:via-green-700 { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:via-green-800 { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:via-green-900 { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:via-teal-100 { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:via-teal-200 { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:via-teal-300 { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:via-teal-400 { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:via-teal-500 { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:via-teal-600 { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:via-teal-700 { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:via-teal-800 { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:via-teal-900 { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:via-blue-100 { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:via-blue-200 { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:via-blue-300 { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:via-blue-400 { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:via-blue-500 { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:via-blue-600 { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:via-blue-700 { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:via-blue-800 { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:via-blue-900 { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:via-indigo-100 { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:via-indigo-200 { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:via-indigo-300 { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:via-indigo-400 { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:via-indigo-500 { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:via-indigo-600 { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:via-indigo-700 { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:via-indigo-800 { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:via-indigo-900 { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:via-purple-100 { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:via-purple-200 { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:via-purple-300 { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:via-purple-400 { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:via-purple-500 { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:via-purple-600 { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:via-purple-700 { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:via-purple-800 { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:via-purple-900 { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:via-pink-100 { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:via-pink-200 { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:via-pink-300 { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:via-pink-400 { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:via-pink-500 { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:via-pink-600 { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:via-pink-700 { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:via-pink-800 { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:via-pink-900 { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:to-transparent { + --gradient-to-color: transparent; + } + + .lg\:to-current { + --gradient-to-color: currentColor; + } + + .lg\:to-black { + --gradient-to-color: #000; + } + + .lg\:to-white { + --gradient-to-color: #fff; + } + + .lg\:to-gray-100 { + --gradient-to-color: #f7fafc; + } + + .lg\:to-gray-200 { + --gradient-to-color: #edf2f7; + } + + .lg\:to-gray-300 { + --gradient-to-color: #e2e8f0; + } + + .lg\:to-gray-400 { + --gradient-to-color: #cbd5e0; + } + + .lg\:to-gray-500 { + --gradient-to-color: #a0aec0; + } + + .lg\:to-gray-600 { + --gradient-to-color: #718096; + } + + .lg\:to-gray-700 { + --gradient-to-color: #4a5568; + } + + .lg\:to-gray-800 { + --gradient-to-color: #2d3748; + } + + .lg\:to-gray-900 { + --gradient-to-color: #1a202c; + } + + .lg\:to-red-100 { + --gradient-to-color: #fff5f5; + } + + .lg\:to-red-200 { + --gradient-to-color: #fed7d7; + } + + .lg\:to-red-300 { + --gradient-to-color: #feb2b2; + } + + .lg\:to-red-400 { + --gradient-to-color: #fc8181; + } + + .lg\:to-red-500 { + --gradient-to-color: #f56565; + } + + .lg\:to-red-600 { + --gradient-to-color: #e53e3e; + } + + .lg\:to-red-700 { + --gradient-to-color: #c53030; + } + + .lg\:to-red-800 { + --gradient-to-color: #9b2c2c; + } + + .lg\:to-red-900 { + --gradient-to-color: #742a2a; + } + + .lg\:to-orange-100 { + --gradient-to-color: #fffaf0; + } + + .lg\:to-orange-200 { + --gradient-to-color: #feebc8; + } + + .lg\:to-orange-300 { + --gradient-to-color: #fbd38d; + } + + .lg\:to-orange-400 { + --gradient-to-color: #f6ad55; + } + + .lg\:to-orange-500 { + --gradient-to-color: #ed8936; + } + + .lg\:to-orange-600 { + --gradient-to-color: #dd6b20; + } + + .lg\:to-orange-700 { + --gradient-to-color: #c05621; + } + + .lg\:to-orange-800 { + --gradient-to-color: #9c4221; + } + + .lg\:to-orange-900 { + --gradient-to-color: #7b341e; + } + + .lg\:to-yellow-100 { + --gradient-to-color: #fffff0; + } + + .lg\:to-yellow-200 { + --gradient-to-color: #fefcbf; + } + + .lg\:to-yellow-300 { + --gradient-to-color: #faf089; + } + + .lg\:to-yellow-400 { + --gradient-to-color: #f6e05e; + } + + .lg\:to-yellow-500 { + --gradient-to-color: #ecc94b; + } + + .lg\:to-yellow-600 { + --gradient-to-color: #d69e2e; + } + + .lg\:to-yellow-700 { + --gradient-to-color: #b7791f; + } + + .lg\:to-yellow-800 { + --gradient-to-color: #975a16; + } + + .lg\:to-yellow-900 { + --gradient-to-color: #744210; + } + + .lg\:to-green-100 { + --gradient-to-color: #f0fff4; + } + + .lg\:to-green-200 { + --gradient-to-color: #c6f6d5; + } + + .lg\:to-green-300 { + --gradient-to-color: #9ae6b4; + } + + .lg\:to-green-400 { + --gradient-to-color: #68d391; + } + + .lg\:to-green-500 { + --gradient-to-color: #48bb78; + } + + .lg\:to-green-600 { + --gradient-to-color: #38a169; + } + + .lg\:to-green-700 { + --gradient-to-color: #2f855a; + } + + .lg\:to-green-800 { + --gradient-to-color: #276749; + } + + .lg\:to-green-900 { + --gradient-to-color: #22543d; + } + + .lg\:to-teal-100 { + --gradient-to-color: #e6fffa; + } + + .lg\:to-teal-200 { + --gradient-to-color: #b2f5ea; + } + + .lg\:to-teal-300 { + --gradient-to-color: #81e6d9; + } + + .lg\:to-teal-400 { + --gradient-to-color: #4fd1c5; + } + + .lg\:to-teal-500 { + --gradient-to-color: #38b2ac; + } + + .lg\:to-teal-600 { + --gradient-to-color: #319795; + } + + .lg\:to-teal-700 { + --gradient-to-color: #2c7a7b; + } + + .lg\:to-teal-800 { + --gradient-to-color: #285e61; + } + + .lg\:to-teal-900 { + --gradient-to-color: #234e52; + } + + .lg\:to-blue-100 { + --gradient-to-color: #ebf8ff; + } + + .lg\:to-blue-200 { + --gradient-to-color: #bee3f8; + } + + .lg\:to-blue-300 { + --gradient-to-color: #90cdf4; + } + + .lg\:to-blue-400 { + --gradient-to-color: #63b3ed; + } + + .lg\:to-blue-500 { + --gradient-to-color: #4299e1; + } + + .lg\:to-blue-600 { + --gradient-to-color: #3182ce; + } + + .lg\:to-blue-700 { + --gradient-to-color: #2b6cb0; + } + + .lg\:to-blue-800 { + --gradient-to-color: #2c5282; + } + + .lg\:to-blue-900 { + --gradient-to-color: #2a4365; + } + + .lg\:to-indigo-100 { + --gradient-to-color: #ebf4ff; + } + + .lg\:to-indigo-200 { + --gradient-to-color: #c3dafe; + } + + .lg\:to-indigo-300 { + --gradient-to-color: #a3bffa; + } + + .lg\:to-indigo-400 { + --gradient-to-color: #7f9cf5; + } + + .lg\:to-indigo-500 { + --gradient-to-color: #667eea; + } + + .lg\:to-indigo-600 { + --gradient-to-color: #5a67d8; + } + + .lg\:to-indigo-700 { + --gradient-to-color: #4c51bf; + } + + .lg\:to-indigo-800 { + --gradient-to-color: #434190; + } + + .lg\:to-indigo-900 { + --gradient-to-color: #3c366b; + } + + .lg\:to-purple-100 { + --gradient-to-color: #faf5ff; + } + + .lg\:to-purple-200 { + --gradient-to-color: #e9d8fd; + } + + .lg\:to-purple-300 { + --gradient-to-color: #d6bcfa; + } + + .lg\:to-purple-400 { + --gradient-to-color: #b794f4; + } + + .lg\:to-purple-500 { + --gradient-to-color: #9f7aea; + } + + .lg\:to-purple-600 { + --gradient-to-color: #805ad5; + } + + .lg\:to-purple-700 { + --gradient-to-color: #6b46c1; + } + + .lg\:to-purple-800 { + --gradient-to-color: #553c9a; + } + + .lg\:to-purple-900 { + --gradient-to-color: #44337a; + } + + .lg\:to-pink-100 { + --gradient-to-color: #fff5f7; + } + + .lg\:to-pink-200 { + --gradient-to-color: #fed7e2; + } + + .lg\:to-pink-300 { + --gradient-to-color: #fbb6ce; + } + + .lg\:to-pink-400 { + --gradient-to-color: #f687b3; + } + + .lg\:to-pink-500 { + --gradient-to-color: #ed64a6; + } + + .lg\:to-pink-600 { + --gradient-to-color: #d53f8c; + } + + .lg\:to-pink-700 { + --gradient-to-color: #b83280; + } + + .lg\:to-pink-800 { + --gradient-to-color: #97266d; + } + + .lg\:to-pink-900 { + --gradient-to-color: #702459; + } + + .lg\:hover\:from-transparent:hover { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:hover\:from-current:hover { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:hover\:from-black:hover { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:hover\:from-white:hover { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:hover\:from-gray-100:hover { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:hover\:from-gray-200:hover { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:hover\:from-gray-300:hover { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:hover\:from-gray-400:hover { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:hover\:from-gray-500:hover { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:hover\:from-gray-600:hover { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:hover\:from-gray-700:hover { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:hover\:from-gray-800:hover { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:hover\:from-gray-900:hover { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:hover\:from-red-100:hover { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:hover\:from-red-200:hover { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:hover\:from-red-300:hover { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:hover\:from-red-400:hover { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:hover\:from-red-500:hover { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:hover\:from-red-600:hover { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:hover\:from-red-700:hover { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:hover\:from-red-800:hover { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:hover\:from-red-900:hover { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:hover\:from-orange-100:hover { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:hover\:from-orange-200:hover { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:hover\:from-orange-300:hover { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:hover\:from-orange-400:hover { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:hover\:from-orange-500:hover { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:hover\:from-orange-600:hover { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:hover\:from-orange-700:hover { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:hover\:from-orange-800:hover { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:hover\:from-orange-900:hover { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:hover\:from-yellow-100:hover { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:hover\:from-yellow-200:hover { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:hover\:from-yellow-300:hover { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:hover\:from-yellow-400:hover { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:hover\:from-yellow-500:hover { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:hover\:from-yellow-600:hover { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:hover\:from-yellow-700:hover { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:hover\:from-yellow-800:hover { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:hover\:from-yellow-900:hover { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:hover\:from-green-100:hover { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:hover\:from-green-200:hover { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:hover\:from-green-300:hover { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:hover\:from-green-400:hover { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:hover\:from-green-500:hover { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:hover\:from-green-600:hover { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:hover\:from-green-700:hover { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:hover\:from-green-800:hover { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:hover\:from-green-900:hover { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:hover\:from-teal-100:hover { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:hover\:from-teal-200:hover { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:hover\:from-teal-300:hover { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:hover\:from-teal-400:hover { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:hover\:from-teal-500:hover { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:hover\:from-teal-600:hover { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:hover\:from-teal-700:hover { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:hover\:from-teal-800:hover { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:hover\:from-teal-900:hover { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:hover\:from-blue-100:hover { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:hover\:from-blue-200:hover { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:hover\:from-blue-300:hover { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:hover\:from-blue-400:hover { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:hover\:from-blue-500:hover { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:hover\:from-blue-600:hover { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:hover\:from-blue-700:hover { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:hover\:from-blue-800:hover { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:hover\:from-blue-900:hover { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:hover\:from-indigo-100:hover { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:hover\:from-indigo-200:hover { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:hover\:from-indigo-300:hover { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:hover\:from-indigo-400:hover { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:hover\:from-indigo-500:hover { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:hover\:from-indigo-600:hover { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:hover\:from-indigo-700:hover { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:hover\:from-indigo-800:hover { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:hover\:from-indigo-900:hover { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:hover\:from-purple-100:hover { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:hover\:from-purple-200:hover { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:hover\:from-purple-300:hover { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:hover\:from-purple-400:hover { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:hover\:from-purple-500:hover { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:hover\:from-purple-600:hover { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:hover\:from-purple-700:hover { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:hover\:from-purple-800:hover { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:hover\:from-purple-900:hover { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:hover\:from-pink-100:hover { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:hover\:from-pink-200:hover { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:hover\:from-pink-300:hover { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:hover\:from-pink-400:hover { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:hover\:from-pink-500:hover { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:hover\:from-pink-600:hover { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:hover\:from-pink-700:hover { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:hover\:from-pink-800:hover { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:hover\:from-pink-900:hover { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:hover\:via-transparent:hover { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:hover\:via-current:hover { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:hover\:via-black:hover { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:hover\:via-white:hover { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:hover\:via-gray-100:hover { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:hover\:via-gray-200:hover { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:hover\:via-gray-300:hover { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:hover\:via-gray-400:hover { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:hover\:via-gray-500:hover { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:hover\:via-gray-600:hover { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:hover\:via-gray-700:hover { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:hover\:via-gray-800:hover { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:hover\:via-gray-900:hover { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:hover\:via-red-100:hover { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:hover\:via-red-200:hover { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:hover\:via-red-300:hover { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:hover\:via-red-400:hover { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:hover\:via-red-500:hover { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:hover\:via-red-600:hover { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:hover\:via-red-700:hover { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:hover\:via-red-800:hover { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:hover\:via-red-900:hover { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:hover\:via-orange-100:hover { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:hover\:via-orange-200:hover { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:hover\:via-orange-300:hover { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:hover\:via-orange-400:hover { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:hover\:via-orange-500:hover { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:hover\:via-orange-600:hover { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:hover\:via-orange-700:hover { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:hover\:via-orange-800:hover { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:hover\:via-orange-900:hover { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:hover\:via-yellow-100:hover { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:hover\:via-yellow-200:hover { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:hover\:via-yellow-300:hover { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:hover\:via-yellow-400:hover { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:hover\:via-yellow-500:hover { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:hover\:via-yellow-600:hover { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:hover\:via-yellow-700:hover { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:hover\:via-yellow-800:hover { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:hover\:via-yellow-900:hover { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:hover\:via-green-100:hover { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:hover\:via-green-200:hover { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:hover\:via-green-300:hover { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:hover\:via-green-400:hover { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:hover\:via-green-500:hover { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:hover\:via-green-600:hover { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:hover\:via-green-700:hover { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:hover\:via-green-800:hover { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:hover\:via-green-900:hover { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:hover\:via-teal-100:hover { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:hover\:via-teal-200:hover { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:hover\:via-teal-300:hover { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:hover\:via-teal-400:hover { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:hover\:via-teal-500:hover { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:hover\:via-teal-600:hover { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:hover\:via-teal-700:hover { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:hover\:via-teal-800:hover { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:hover\:via-teal-900:hover { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:hover\:via-blue-100:hover { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:hover\:via-blue-200:hover { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:hover\:via-blue-300:hover { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:hover\:via-blue-400:hover { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:hover\:via-blue-500:hover { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:hover\:via-blue-600:hover { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:hover\:via-blue-700:hover { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:hover\:via-blue-800:hover { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:hover\:via-blue-900:hover { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:hover\:via-indigo-100:hover { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:hover\:via-indigo-200:hover { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:hover\:via-indigo-300:hover { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:hover\:via-indigo-400:hover { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:hover\:via-indigo-500:hover { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:hover\:via-indigo-600:hover { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:hover\:via-indigo-700:hover { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:hover\:via-indigo-800:hover { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:hover\:via-indigo-900:hover { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:hover\:via-purple-100:hover { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:hover\:via-purple-200:hover { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:hover\:via-purple-300:hover { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:hover\:via-purple-400:hover { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:hover\:via-purple-500:hover { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:hover\:via-purple-600:hover { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:hover\:via-purple-700:hover { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:hover\:via-purple-800:hover { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:hover\:via-purple-900:hover { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:hover\:via-pink-100:hover { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:hover\:via-pink-200:hover { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:hover\:via-pink-300:hover { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:hover\:via-pink-400:hover { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:hover\:via-pink-500:hover { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:hover\:via-pink-600:hover { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:hover\:via-pink-700:hover { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:hover\:via-pink-800:hover { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:hover\:via-pink-900:hover { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:hover\:to-transparent:hover { + --gradient-to-color: transparent; + } + + .lg\:hover\:to-current:hover { + --gradient-to-color: currentColor; + } + + .lg\:hover\:to-black:hover { + --gradient-to-color: #000; + } + + .lg\:hover\:to-white:hover { + --gradient-to-color: #fff; + } + + .lg\:hover\:to-gray-100:hover { + --gradient-to-color: #f7fafc; + } + + .lg\:hover\:to-gray-200:hover { + --gradient-to-color: #edf2f7; + } + + .lg\:hover\:to-gray-300:hover { + --gradient-to-color: #e2e8f0; + } + + .lg\:hover\:to-gray-400:hover { + --gradient-to-color: #cbd5e0; + } + + .lg\:hover\:to-gray-500:hover { + --gradient-to-color: #a0aec0; + } + + .lg\:hover\:to-gray-600:hover { + --gradient-to-color: #718096; + } + + .lg\:hover\:to-gray-700:hover { + --gradient-to-color: #4a5568; + } + + .lg\:hover\:to-gray-800:hover { + --gradient-to-color: #2d3748; + } + + .lg\:hover\:to-gray-900:hover { + --gradient-to-color: #1a202c; + } + + .lg\:hover\:to-red-100:hover { + --gradient-to-color: #fff5f5; + } + + .lg\:hover\:to-red-200:hover { + --gradient-to-color: #fed7d7; + } + + .lg\:hover\:to-red-300:hover { + --gradient-to-color: #feb2b2; + } + + .lg\:hover\:to-red-400:hover { + --gradient-to-color: #fc8181; + } + + .lg\:hover\:to-red-500:hover { + --gradient-to-color: #f56565; + } + + .lg\:hover\:to-red-600:hover { + --gradient-to-color: #e53e3e; + } + + .lg\:hover\:to-red-700:hover { + --gradient-to-color: #c53030; + } + + .lg\:hover\:to-red-800:hover { + --gradient-to-color: #9b2c2c; + } + + .lg\:hover\:to-red-900:hover { + --gradient-to-color: #742a2a; + } + + .lg\:hover\:to-orange-100:hover { + --gradient-to-color: #fffaf0; + } + + .lg\:hover\:to-orange-200:hover { + --gradient-to-color: #feebc8; + } + + .lg\:hover\:to-orange-300:hover { + --gradient-to-color: #fbd38d; + } + + .lg\:hover\:to-orange-400:hover { + --gradient-to-color: #f6ad55; + } + + .lg\:hover\:to-orange-500:hover { + --gradient-to-color: #ed8936; + } + + .lg\:hover\:to-orange-600:hover { + --gradient-to-color: #dd6b20; + } + + .lg\:hover\:to-orange-700:hover { + --gradient-to-color: #c05621; + } + + .lg\:hover\:to-orange-800:hover { + --gradient-to-color: #9c4221; + } + + .lg\:hover\:to-orange-900:hover { + --gradient-to-color: #7b341e; + } + + .lg\:hover\:to-yellow-100:hover { + --gradient-to-color: #fffff0; + } + + .lg\:hover\:to-yellow-200:hover { + --gradient-to-color: #fefcbf; + } + + .lg\:hover\:to-yellow-300:hover { + --gradient-to-color: #faf089; + } + + .lg\:hover\:to-yellow-400:hover { + --gradient-to-color: #f6e05e; + } + + .lg\:hover\:to-yellow-500:hover { + --gradient-to-color: #ecc94b; + } + + .lg\:hover\:to-yellow-600:hover { + --gradient-to-color: #d69e2e; + } + + .lg\:hover\:to-yellow-700:hover { + --gradient-to-color: #b7791f; + } + + .lg\:hover\:to-yellow-800:hover { + --gradient-to-color: #975a16; + } + + .lg\:hover\:to-yellow-900:hover { + --gradient-to-color: #744210; + } + + .lg\:hover\:to-green-100:hover { + --gradient-to-color: #f0fff4; + } + + .lg\:hover\:to-green-200:hover { + --gradient-to-color: #c6f6d5; + } + + .lg\:hover\:to-green-300:hover { + --gradient-to-color: #9ae6b4; + } + + .lg\:hover\:to-green-400:hover { + --gradient-to-color: #68d391; + } + + .lg\:hover\:to-green-500:hover { + --gradient-to-color: #48bb78; + } + + .lg\:hover\:to-green-600:hover { + --gradient-to-color: #38a169; + } + + .lg\:hover\:to-green-700:hover { + --gradient-to-color: #2f855a; + } + + .lg\:hover\:to-green-800:hover { + --gradient-to-color: #276749; + } + + .lg\:hover\:to-green-900:hover { + --gradient-to-color: #22543d; + } + + .lg\:hover\:to-teal-100:hover { + --gradient-to-color: #e6fffa; + } + + .lg\:hover\:to-teal-200:hover { + --gradient-to-color: #b2f5ea; + } + + .lg\:hover\:to-teal-300:hover { + --gradient-to-color: #81e6d9; + } + + .lg\:hover\:to-teal-400:hover { + --gradient-to-color: #4fd1c5; + } + + .lg\:hover\:to-teal-500:hover { + --gradient-to-color: #38b2ac; + } + + .lg\:hover\:to-teal-600:hover { + --gradient-to-color: #319795; + } + + .lg\:hover\:to-teal-700:hover { + --gradient-to-color: #2c7a7b; + } + + .lg\:hover\:to-teal-800:hover { + --gradient-to-color: #285e61; + } + + .lg\:hover\:to-teal-900:hover { + --gradient-to-color: #234e52; + } + + .lg\:hover\:to-blue-100:hover { + --gradient-to-color: #ebf8ff; + } + + .lg\:hover\:to-blue-200:hover { + --gradient-to-color: #bee3f8; + } + + .lg\:hover\:to-blue-300:hover { + --gradient-to-color: #90cdf4; + } + + .lg\:hover\:to-blue-400:hover { + --gradient-to-color: #63b3ed; + } + + .lg\:hover\:to-blue-500:hover { + --gradient-to-color: #4299e1; + } + + .lg\:hover\:to-blue-600:hover { + --gradient-to-color: #3182ce; + } + + .lg\:hover\:to-blue-700:hover { + --gradient-to-color: #2b6cb0; + } + + .lg\:hover\:to-blue-800:hover { + --gradient-to-color: #2c5282; + } + + .lg\:hover\:to-blue-900:hover { + --gradient-to-color: #2a4365; + } + + .lg\:hover\:to-indigo-100:hover { + --gradient-to-color: #ebf4ff; + } + + .lg\:hover\:to-indigo-200:hover { + --gradient-to-color: #c3dafe; + } + + .lg\:hover\:to-indigo-300:hover { + --gradient-to-color: #a3bffa; + } + + .lg\:hover\:to-indigo-400:hover { + --gradient-to-color: #7f9cf5; + } + + .lg\:hover\:to-indigo-500:hover { + --gradient-to-color: #667eea; + } + + .lg\:hover\:to-indigo-600:hover { + --gradient-to-color: #5a67d8; + } + + .lg\:hover\:to-indigo-700:hover { + --gradient-to-color: #4c51bf; + } + + .lg\:hover\:to-indigo-800:hover { + --gradient-to-color: #434190; + } + + .lg\:hover\:to-indigo-900:hover { + --gradient-to-color: #3c366b; + } + + .lg\:hover\:to-purple-100:hover { + --gradient-to-color: #faf5ff; + } + + .lg\:hover\:to-purple-200:hover { + --gradient-to-color: #e9d8fd; + } + + .lg\:hover\:to-purple-300:hover { + --gradient-to-color: #d6bcfa; + } + + .lg\:hover\:to-purple-400:hover { + --gradient-to-color: #b794f4; + } + + .lg\:hover\:to-purple-500:hover { + --gradient-to-color: #9f7aea; + } + + .lg\:hover\:to-purple-600:hover { + --gradient-to-color: #805ad5; + } + + .lg\:hover\:to-purple-700:hover { + --gradient-to-color: #6b46c1; + } + + .lg\:hover\:to-purple-800:hover { + --gradient-to-color: #553c9a; + } + + .lg\:hover\:to-purple-900:hover { + --gradient-to-color: #44337a; + } + + .lg\:hover\:to-pink-100:hover { + --gradient-to-color: #fff5f7; + } + + .lg\:hover\:to-pink-200:hover { + --gradient-to-color: #fed7e2; + } + + .lg\:hover\:to-pink-300:hover { + --gradient-to-color: #fbb6ce; + } + + .lg\:hover\:to-pink-400:hover { + --gradient-to-color: #f687b3; + } + + .lg\:hover\:to-pink-500:hover { + --gradient-to-color: #ed64a6; + } + + .lg\:hover\:to-pink-600:hover { + --gradient-to-color: #d53f8c; + } + + .lg\:hover\:to-pink-700:hover { + --gradient-to-color: #b83280; + } + + .lg\:hover\:to-pink-800:hover { + --gradient-to-color: #97266d; + } + + .lg\:hover\:to-pink-900:hover { + --gradient-to-color: #702459; + } + + .lg\:focus\:from-transparent:focus { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:focus\:from-current:focus { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:focus\:from-black:focus { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:focus\:from-white:focus { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:focus\:from-gray-100:focus { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:focus\:from-gray-200:focus { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:focus\:from-gray-300:focus { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:focus\:from-gray-400:focus { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:focus\:from-gray-500:focus { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:focus\:from-gray-600:focus { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:focus\:from-gray-700:focus { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:focus\:from-gray-800:focus { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:focus\:from-gray-900:focus { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:focus\:from-red-100:focus { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:focus\:from-red-200:focus { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:focus\:from-red-300:focus { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:focus\:from-red-400:focus { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:focus\:from-red-500:focus { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:focus\:from-red-600:focus { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:focus\:from-red-700:focus { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:focus\:from-red-800:focus { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:focus\:from-red-900:focus { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:focus\:from-orange-100:focus { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:focus\:from-orange-200:focus { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:focus\:from-orange-300:focus { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:focus\:from-orange-400:focus { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:focus\:from-orange-500:focus { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:focus\:from-orange-600:focus { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:focus\:from-orange-700:focus { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:focus\:from-orange-800:focus { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:focus\:from-orange-900:focus { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:focus\:from-yellow-100:focus { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:focus\:from-yellow-200:focus { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:focus\:from-yellow-300:focus { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:focus\:from-yellow-400:focus { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:focus\:from-yellow-500:focus { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:focus\:from-yellow-600:focus { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:focus\:from-yellow-700:focus { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:focus\:from-yellow-800:focus { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:focus\:from-yellow-900:focus { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:focus\:from-green-100:focus { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:focus\:from-green-200:focus { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:focus\:from-green-300:focus { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:focus\:from-green-400:focus { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:focus\:from-green-500:focus { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:focus\:from-green-600:focus { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:focus\:from-green-700:focus { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:focus\:from-green-800:focus { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:focus\:from-green-900:focus { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:focus\:from-teal-100:focus { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:focus\:from-teal-200:focus { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:focus\:from-teal-300:focus { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:focus\:from-teal-400:focus { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:focus\:from-teal-500:focus { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:focus\:from-teal-600:focus { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:focus\:from-teal-700:focus { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:focus\:from-teal-800:focus { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:focus\:from-teal-900:focus { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:focus\:from-blue-100:focus { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:focus\:from-blue-200:focus { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:focus\:from-blue-300:focus { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:focus\:from-blue-400:focus { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:focus\:from-blue-500:focus { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:focus\:from-blue-600:focus { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:focus\:from-blue-700:focus { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:focus\:from-blue-800:focus { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:focus\:from-blue-900:focus { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:focus\:from-indigo-100:focus { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:focus\:from-indigo-200:focus { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:focus\:from-indigo-300:focus { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:focus\:from-indigo-400:focus { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:focus\:from-indigo-500:focus { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:focus\:from-indigo-600:focus { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:focus\:from-indigo-700:focus { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:focus\:from-indigo-800:focus { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:focus\:from-indigo-900:focus { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:focus\:from-purple-100:focus { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:focus\:from-purple-200:focus { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:focus\:from-purple-300:focus { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:focus\:from-purple-400:focus { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:focus\:from-purple-500:focus { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:focus\:from-purple-600:focus { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:focus\:from-purple-700:focus { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:focus\:from-purple-800:focus { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:focus\:from-purple-900:focus { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:focus\:from-pink-100:focus { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:focus\:from-pink-200:focus { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:focus\:from-pink-300:focus { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:focus\:from-pink-400:focus { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:focus\:from-pink-500:focus { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:focus\:from-pink-600:focus { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:focus\:from-pink-700:focus { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:focus\:from-pink-800:focus { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:focus\:from-pink-900:focus { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:focus\:via-transparent:focus { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:focus\:via-current:focus { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:focus\:via-black:focus { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .lg\:focus\:via-white:focus { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .lg\:focus\:via-gray-100:focus { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .lg\:focus\:via-gray-200:focus { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .lg\:focus\:via-gray-300:focus { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .lg\:focus\:via-gray-400:focus { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .lg\:focus\:via-gray-500:focus { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .lg\:focus\:via-gray-600:focus { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .lg\:focus\:via-gray-700:focus { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .lg\:focus\:via-gray-800:focus { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .lg\:focus\:via-gray-900:focus { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .lg\:focus\:via-red-100:focus { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .lg\:focus\:via-red-200:focus { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .lg\:focus\:via-red-300:focus { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .lg\:focus\:via-red-400:focus { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .lg\:focus\:via-red-500:focus { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .lg\:focus\:via-red-600:focus { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .lg\:focus\:via-red-700:focus { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .lg\:focus\:via-red-800:focus { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .lg\:focus\:via-red-900:focus { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .lg\:focus\:via-orange-100:focus { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .lg\:focus\:via-orange-200:focus { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .lg\:focus\:via-orange-300:focus { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .lg\:focus\:via-orange-400:focus { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .lg\:focus\:via-orange-500:focus { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .lg\:focus\:via-orange-600:focus { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .lg\:focus\:via-orange-700:focus { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .lg\:focus\:via-orange-800:focus { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .lg\:focus\:via-orange-900:focus { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .lg\:focus\:via-yellow-100:focus { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .lg\:focus\:via-yellow-200:focus { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .lg\:focus\:via-yellow-300:focus { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .lg\:focus\:via-yellow-400:focus { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .lg\:focus\:via-yellow-500:focus { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .lg\:focus\:via-yellow-600:focus { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .lg\:focus\:via-yellow-700:focus { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .lg\:focus\:via-yellow-800:focus { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .lg\:focus\:via-yellow-900:focus { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .lg\:focus\:via-green-100:focus { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .lg\:focus\:via-green-200:focus { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .lg\:focus\:via-green-300:focus { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .lg\:focus\:via-green-400:focus { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .lg\:focus\:via-green-500:focus { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .lg\:focus\:via-green-600:focus { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .lg\:focus\:via-green-700:focus { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .lg\:focus\:via-green-800:focus { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .lg\:focus\:via-green-900:focus { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .lg\:focus\:via-teal-100:focus { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .lg\:focus\:via-teal-200:focus { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .lg\:focus\:via-teal-300:focus { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .lg\:focus\:via-teal-400:focus { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .lg\:focus\:via-teal-500:focus { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .lg\:focus\:via-teal-600:focus { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .lg\:focus\:via-teal-700:focus { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .lg\:focus\:via-teal-800:focus { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .lg\:focus\:via-teal-900:focus { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .lg\:focus\:via-blue-100:focus { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .lg\:focus\:via-blue-200:focus { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .lg\:focus\:via-blue-300:focus { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .lg\:focus\:via-blue-400:focus { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .lg\:focus\:via-blue-500:focus { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .lg\:focus\:via-blue-600:focus { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .lg\:focus\:via-blue-700:focus { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .lg\:focus\:via-blue-800:focus { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .lg\:focus\:via-blue-900:focus { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .lg\:focus\:via-indigo-100:focus { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .lg\:focus\:via-indigo-200:focus { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .lg\:focus\:via-indigo-300:focus { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .lg\:focus\:via-indigo-400:focus { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .lg\:focus\:via-indigo-500:focus { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .lg\:focus\:via-indigo-600:focus { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .lg\:focus\:via-indigo-700:focus { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .lg\:focus\:via-indigo-800:focus { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .lg\:focus\:via-indigo-900:focus { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .lg\:focus\:via-purple-100:focus { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .lg\:focus\:via-purple-200:focus { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .lg\:focus\:via-purple-300:focus { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .lg\:focus\:via-purple-400:focus { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .lg\:focus\:via-purple-500:focus { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .lg\:focus\:via-purple-600:focus { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .lg\:focus\:via-purple-700:focus { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .lg\:focus\:via-purple-800:focus { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .lg\:focus\:via-purple-900:focus { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .lg\:focus\:via-pink-100:focus { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .lg\:focus\:via-pink-200:focus { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .lg\:focus\:via-pink-300:focus { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .lg\:focus\:via-pink-400:focus { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .lg\:focus\:via-pink-500:focus { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .lg\:focus\:via-pink-600:focus { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .lg\:focus\:via-pink-700:focus { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .lg\:focus\:via-pink-800:focus { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .lg\:focus\:via-pink-900:focus { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .lg\:focus\:to-transparent:focus { + --gradient-to-color: transparent; + } + + .lg\:focus\:to-current:focus { + --gradient-to-color: currentColor; + } + + .lg\:focus\:to-black:focus { + --gradient-to-color: #000; + } + + .lg\:focus\:to-white:focus { + --gradient-to-color: #fff; + } + + .lg\:focus\:to-gray-100:focus { + --gradient-to-color: #f7fafc; + } + + .lg\:focus\:to-gray-200:focus { + --gradient-to-color: #edf2f7; + } + + .lg\:focus\:to-gray-300:focus { + --gradient-to-color: #e2e8f0; + } + + .lg\:focus\:to-gray-400:focus { + --gradient-to-color: #cbd5e0; + } + + .lg\:focus\:to-gray-500:focus { + --gradient-to-color: #a0aec0; + } + + .lg\:focus\:to-gray-600:focus { + --gradient-to-color: #718096; + } + + .lg\:focus\:to-gray-700:focus { + --gradient-to-color: #4a5568; + } + + .lg\:focus\:to-gray-800:focus { + --gradient-to-color: #2d3748; + } + + .lg\:focus\:to-gray-900:focus { + --gradient-to-color: #1a202c; + } + + .lg\:focus\:to-red-100:focus { + --gradient-to-color: #fff5f5; + } + + .lg\:focus\:to-red-200:focus { + --gradient-to-color: #fed7d7; + } + + .lg\:focus\:to-red-300:focus { + --gradient-to-color: #feb2b2; + } + + .lg\:focus\:to-red-400:focus { + --gradient-to-color: #fc8181; + } + + .lg\:focus\:to-red-500:focus { + --gradient-to-color: #f56565; + } + + .lg\:focus\:to-red-600:focus { + --gradient-to-color: #e53e3e; + } + + .lg\:focus\:to-red-700:focus { + --gradient-to-color: #c53030; + } + + .lg\:focus\:to-red-800:focus { + --gradient-to-color: #9b2c2c; + } + + .lg\:focus\:to-red-900:focus { + --gradient-to-color: #742a2a; + } + + .lg\:focus\:to-orange-100:focus { + --gradient-to-color: #fffaf0; + } + + .lg\:focus\:to-orange-200:focus { + --gradient-to-color: #feebc8; + } + + .lg\:focus\:to-orange-300:focus { + --gradient-to-color: #fbd38d; + } + + .lg\:focus\:to-orange-400:focus { + --gradient-to-color: #f6ad55; + } + + .lg\:focus\:to-orange-500:focus { + --gradient-to-color: #ed8936; + } + + .lg\:focus\:to-orange-600:focus { + --gradient-to-color: #dd6b20; + } + + .lg\:focus\:to-orange-700:focus { + --gradient-to-color: #c05621; + } + + .lg\:focus\:to-orange-800:focus { + --gradient-to-color: #9c4221; + } + + .lg\:focus\:to-orange-900:focus { + --gradient-to-color: #7b341e; + } + + .lg\:focus\:to-yellow-100:focus { + --gradient-to-color: #fffff0; + } + + .lg\:focus\:to-yellow-200:focus { + --gradient-to-color: #fefcbf; + } + + .lg\:focus\:to-yellow-300:focus { + --gradient-to-color: #faf089; + } + + .lg\:focus\:to-yellow-400:focus { + --gradient-to-color: #f6e05e; + } + + .lg\:focus\:to-yellow-500:focus { + --gradient-to-color: #ecc94b; + } + + .lg\:focus\:to-yellow-600:focus { + --gradient-to-color: #d69e2e; + } + + .lg\:focus\:to-yellow-700:focus { + --gradient-to-color: #b7791f; + } + + .lg\:focus\:to-yellow-800:focus { + --gradient-to-color: #975a16; + } + + .lg\:focus\:to-yellow-900:focus { + --gradient-to-color: #744210; + } + + .lg\:focus\:to-green-100:focus { + --gradient-to-color: #f0fff4; + } + + .lg\:focus\:to-green-200:focus { + --gradient-to-color: #c6f6d5; + } + + .lg\:focus\:to-green-300:focus { + --gradient-to-color: #9ae6b4; + } + + .lg\:focus\:to-green-400:focus { + --gradient-to-color: #68d391; + } + + .lg\:focus\:to-green-500:focus { + --gradient-to-color: #48bb78; + } + + .lg\:focus\:to-green-600:focus { + --gradient-to-color: #38a169; + } + + .lg\:focus\:to-green-700:focus { + --gradient-to-color: #2f855a; + } + + .lg\:focus\:to-green-800:focus { + --gradient-to-color: #276749; + } + + .lg\:focus\:to-green-900:focus { + --gradient-to-color: #22543d; + } + + .lg\:focus\:to-teal-100:focus { + --gradient-to-color: #e6fffa; + } + + .lg\:focus\:to-teal-200:focus { + --gradient-to-color: #b2f5ea; + } + + .lg\:focus\:to-teal-300:focus { + --gradient-to-color: #81e6d9; + } + + .lg\:focus\:to-teal-400:focus { + --gradient-to-color: #4fd1c5; + } + + .lg\:focus\:to-teal-500:focus { + --gradient-to-color: #38b2ac; + } + + .lg\:focus\:to-teal-600:focus { + --gradient-to-color: #319795; + } + + .lg\:focus\:to-teal-700:focus { + --gradient-to-color: #2c7a7b; + } + + .lg\:focus\:to-teal-800:focus { + --gradient-to-color: #285e61; + } + + .lg\:focus\:to-teal-900:focus { + --gradient-to-color: #234e52; + } + + .lg\:focus\:to-blue-100:focus { + --gradient-to-color: #ebf8ff; + } + + .lg\:focus\:to-blue-200:focus { + --gradient-to-color: #bee3f8; + } + + .lg\:focus\:to-blue-300:focus { + --gradient-to-color: #90cdf4; + } + + .lg\:focus\:to-blue-400:focus { + --gradient-to-color: #63b3ed; + } + + .lg\:focus\:to-blue-500:focus { + --gradient-to-color: #4299e1; + } + + .lg\:focus\:to-blue-600:focus { + --gradient-to-color: #3182ce; + } + + .lg\:focus\:to-blue-700:focus { + --gradient-to-color: #2b6cb0; + } + + .lg\:focus\:to-blue-800:focus { + --gradient-to-color: #2c5282; + } + + .lg\:focus\:to-blue-900:focus { + --gradient-to-color: #2a4365; + } + + .lg\:focus\:to-indigo-100:focus { + --gradient-to-color: #ebf4ff; + } + + .lg\:focus\:to-indigo-200:focus { + --gradient-to-color: #c3dafe; + } + + .lg\:focus\:to-indigo-300:focus { + --gradient-to-color: #a3bffa; + } + + .lg\:focus\:to-indigo-400:focus { + --gradient-to-color: #7f9cf5; + } + + .lg\:focus\:to-indigo-500:focus { + --gradient-to-color: #667eea; + } + + .lg\:focus\:to-indigo-600:focus { + --gradient-to-color: #5a67d8; + } + + .lg\:focus\:to-indigo-700:focus { + --gradient-to-color: #4c51bf; + } + + .lg\:focus\:to-indigo-800:focus { + --gradient-to-color: #434190; + } + + .lg\:focus\:to-indigo-900:focus { + --gradient-to-color: #3c366b; + } + + .lg\:focus\:to-purple-100:focus { + --gradient-to-color: #faf5ff; + } + + .lg\:focus\:to-purple-200:focus { + --gradient-to-color: #e9d8fd; + } + + .lg\:focus\:to-purple-300:focus { + --gradient-to-color: #d6bcfa; + } + + .lg\:focus\:to-purple-400:focus { + --gradient-to-color: #b794f4; + } + + .lg\:focus\:to-purple-500:focus { + --gradient-to-color: #9f7aea; + } + + .lg\:focus\:to-purple-600:focus { + --gradient-to-color: #805ad5; + } + + .lg\:focus\:to-purple-700:focus { + --gradient-to-color: #6b46c1; + } + + .lg\:focus\:to-purple-800:focus { + --gradient-to-color: #553c9a; + } + + .lg\:focus\:to-purple-900:focus { + --gradient-to-color: #44337a; + } + + .lg\:focus\:to-pink-100:focus { + --gradient-to-color: #fff5f7; + } + + .lg\:focus\:to-pink-200:focus { + --gradient-to-color: #fed7e2; + } + + .lg\:focus\:to-pink-300:focus { + --gradient-to-color: #fbb6ce; + } + + .lg\:focus\:to-pink-400:focus { + --gradient-to-color: #f687b3; + } + + .lg\:focus\:to-pink-500:focus { + --gradient-to-color: #ed64a6; + } + + .lg\:focus\:to-pink-600:focus { + --gradient-to-color: #d53f8c; + } + + .lg\:focus\:to-pink-700:focus { + --gradient-to-color: #b83280; + } + + .lg\:focus\:to-pink-800:focus { + --gradient-to-color: #97266d; + } + + .lg\:focus\:to-pink-900:focus { + --gradient-to-color: #702459; + } + + .lg\:bg-opacity-0 { + --bg-opacity: 0; + } + + .lg\:bg-opacity-25 { + --bg-opacity: 0.25; + } + + .lg\:bg-opacity-50 { + --bg-opacity: 0.5; + } + + .lg\:bg-opacity-75 { + --bg-opacity: 0.75; + } + + .lg\:bg-opacity-100 { + --bg-opacity: 1; + } + + .lg\:hover\:bg-opacity-0:hover { + --bg-opacity: 0; + } + + .lg\:hover\:bg-opacity-25:hover { + --bg-opacity: 0.25; + } + + .lg\:hover\:bg-opacity-50:hover { + --bg-opacity: 0.5; + } + + .lg\:hover\:bg-opacity-75:hover { + --bg-opacity: 0.75; + } + + .lg\:hover\:bg-opacity-100:hover { + --bg-opacity: 1; + } + + .lg\:focus\:bg-opacity-0:focus { + --bg-opacity: 0; + } + + .lg\:focus\:bg-opacity-25:focus { + --bg-opacity: 0.25; + } + + .lg\:focus\:bg-opacity-50:focus { + --bg-opacity: 0.5; + } + + .lg\:focus\:bg-opacity-75:focus { + --bg-opacity: 0.75; + } + + .lg\:focus\:bg-opacity-100:focus { + --bg-opacity: 1; + } + + .lg\:bg-bottom { + background-position: bottom; + } + + .lg\:bg-center { + background-position: center; + } + + .lg\:bg-left { + background-position: left; + } + + .lg\:bg-left-bottom { + background-position: left bottom; + } + + .lg\:bg-left-top { + background-position: left top; + } + + .lg\:bg-right { + background-position: right; + } + + .lg\:bg-right-bottom { + background-position: right bottom; + } + + .lg\:bg-right-top { + background-position: right top; + } + + .lg\:bg-top { + background-position: top; + } + + .lg\:bg-repeat { + background-repeat: repeat; + } + + .lg\:bg-no-repeat { + background-repeat: no-repeat; + } + + .lg\:bg-repeat-x { + background-repeat: repeat-x; + } + + .lg\:bg-repeat-y { + background-repeat: repeat-y; + } + + .lg\:bg-repeat-round { + background-repeat: round; + } + + .lg\:bg-repeat-space { + background-repeat: space; + } + + .lg\:bg-auto { + background-size: auto; + } + + .lg\:bg-cover { + background-size: cover; + } + + .lg\:bg-contain { + background-size: contain; + } + + .lg\:border-collapse { + border-collapse: collapse; + } + + .lg\:border-separate { + border-collapse: separate; + } + + .lg\:border-transparent { + border-color: transparent; + } + + .lg\:border-current { + border-color: currentColor; + } + + .lg\:border-black { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .lg\:border-white { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .lg\:border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .lg\:border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .lg\:border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .lg\:border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .lg\:border-gray-500 { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .lg\:border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .lg\:border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .lg\:border-gray-800 { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .lg\:border-gray-900 { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .lg\:border-red-100 { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .lg\:border-red-200 { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .lg\:border-red-300 { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .lg\:border-red-400 { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .lg\:border-red-500 { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .lg\:border-red-600 { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .lg\:border-red-700 { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .lg\:border-red-800 { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .lg\:border-red-900 { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .lg\:border-orange-100 { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .lg\:border-orange-200 { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .lg\:border-orange-300 { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .lg\:border-orange-400 { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .lg\:border-orange-500 { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .lg\:border-orange-600 { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .lg\:border-orange-700 { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .lg\:border-orange-800 { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .lg\:border-orange-900 { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .lg\:border-yellow-100 { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .lg\:border-yellow-200 { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .lg\:border-yellow-300 { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .lg\:border-yellow-400 { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .lg\:border-yellow-500 { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .lg\:border-yellow-600 { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .lg\:border-yellow-700 { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .lg\:border-yellow-800 { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .lg\:border-yellow-900 { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .lg\:border-green-100 { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .lg\:border-green-200 { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .lg\:border-green-300 { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .lg\:border-green-400 { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .lg\:border-green-500 { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .lg\:border-green-600 { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .lg\:border-green-700 { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .lg\:border-green-800 { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .lg\:border-green-900 { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .lg\:border-teal-100 { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .lg\:border-teal-200 { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .lg\:border-teal-300 { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .lg\:border-teal-400 { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .lg\:border-teal-500 { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .lg\:border-teal-600 { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .lg\:border-teal-700 { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .lg\:border-teal-800 { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .lg\:border-teal-900 { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .lg\:border-blue-100 { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .lg\:border-blue-200 { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .lg\:border-blue-300 { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .lg\:border-blue-400 { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .lg\:border-blue-500 { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .lg\:border-blue-600 { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .lg\:border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .lg\:border-blue-800 { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .lg\:border-blue-900 { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .lg\:border-indigo-100 { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .lg\:border-indigo-200 { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .lg\:border-indigo-300 { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .lg\:border-indigo-400 { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .lg\:border-indigo-500 { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .lg\:border-indigo-600 { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .lg\:border-indigo-700 { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .lg\:border-indigo-800 { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .lg\:border-indigo-900 { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .lg\:border-purple-100 { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .lg\:border-purple-200 { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .lg\:border-purple-300 { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .lg\:border-purple-400 { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .lg\:border-purple-500 { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .lg\:border-purple-600 { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .lg\:border-purple-700 { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .lg\:border-purple-800 { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .lg\:border-purple-900 { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .lg\:border-pink-100 { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .lg\:border-pink-200 { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .lg\:border-pink-300 { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .lg\:border-pink-400 { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .lg\:border-pink-500 { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .lg\:border-pink-600 { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .lg\:border-pink-700 { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .lg\:border-pink-800 { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .lg\:border-pink-900 { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .lg\:hover\:border-transparent:hover { + border-color: transparent; + } + + .lg\:hover\:border-current:hover { + border-color: currentColor; + } + + .lg\:hover\:border-black:hover { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .lg\:hover\:border-white:hover { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .lg\:hover\:border-gray-100:hover { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .lg\:hover\:border-gray-200:hover { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .lg\:hover\:border-gray-300:hover { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .lg\:hover\:border-gray-400:hover { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .lg\:hover\:border-gray-500:hover { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .lg\:hover\:border-gray-600:hover { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .lg\:hover\:border-gray-700:hover { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .lg\:hover\:border-gray-800:hover { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .lg\:hover\:border-gray-900:hover { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .lg\:hover\:border-red-100:hover { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .lg\:hover\:border-red-200:hover { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .lg\:hover\:border-red-300:hover { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .lg\:hover\:border-red-400:hover { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .lg\:hover\:border-red-500:hover { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .lg\:hover\:border-red-600:hover { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .lg\:hover\:border-red-700:hover { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .lg\:hover\:border-red-800:hover { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .lg\:hover\:border-red-900:hover { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .lg\:hover\:border-orange-100:hover { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .lg\:hover\:border-orange-200:hover { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .lg\:hover\:border-orange-300:hover { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .lg\:hover\:border-orange-400:hover { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .lg\:hover\:border-orange-500:hover { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .lg\:hover\:border-orange-600:hover { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .lg\:hover\:border-orange-700:hover { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .lg\:hover\:border-orange-800:hover { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .lg\:hover\:border-orange-900:hover { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-100:hover { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-200:hover { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-300:hover { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-400:hover { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-500:hover { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-600:hover { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-700:hover { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-800:hover { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .lg\:hover\:border-yellow-900:hover { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .lg\:hover\:border-green-100:hover { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .lg\:hover\:border-green-200:hover { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .lg\:hover\:border-green-300:hover { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .lg\:hover\:border-green-400:hover { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .lg\:hover\:border-green-500:hover { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .lg\:hover\:border-green-600:hover { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .lg\:hover\:border-green-700:hover { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .lg\:hover\:border-green-800:hover { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .lg\:hover\:border-green-900:hover { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .lg\:hover\:border-teal-100:hover { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .lg\:hover\:border-teal-200:hover { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .lg\:hover\:border-teal-300:hover { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .lg\:hover\:border-teal-400:hover { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .lg\:hover\:border-teal-500:hover { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .lg\:hover\:border-teal-600:hover { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .lg\:hover\:border-teal-700:hover { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .lg\:hover\:border-teal-800:hover { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .lg\:hover\:border-teal-900:hover { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .lg\:hover\:border-blue-100:hover { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .lg\:hover\:border-blue-200:hover { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .lg\:hover\:border-blue-300:hover { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .lg\:hover\:border-blue-400:hover { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .lg\:hover\:border-blue-500:hover { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .lg\:hover\:border-blue-600:hover { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .lg\:hover\:border-blue-700:hover { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .lg\:hover\:border-blue-800:hover { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .lg\:hover\:border-blue-900:hover { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-100:hover { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-200:hover { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-300:hover { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-400:hover { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-500:hover { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-600:hover { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-700:hover { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-800:hover { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .lg\:hover\:border-indigo-900:hover { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .lg\:hover\:border-purple-100:hover { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .lg\:hover\:border-purple-200:hover { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .lg\:hover\:border-purple-300:hover { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .lg\:hover\:border-purple-400:hover { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .lg\:hover\:border-purple-500:hover { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .lg\:hover\:border-purple-600:hover { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .lg\:hover\:border-purple-700:hover { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .lg\:hover\:border-purple-800:hover { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .lg\:hover\:border-purple-900:hover { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .lg\:hover\:border-pink-100:hover { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .lg\:hover\:border-pink-200:hover { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .lg\:hover\:border-pink-300:hover { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .lg\:hover\:border-pink-400:hover { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .lg\:hover\:border-pink-500:hover { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .lg\:hover\:border-pink-600:hover { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .lg\:hover\:border-pink-700:hover { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .lg\:hover\:border-pink-800:hover { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .lg\:hover\:border-pink-900:hover { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .lg\:focus\:border-transparent:focus { + border-color: transparent; + } + + .lg\:focus\:border-current:focus { + border-color: currentColor; + } + + .lg\:focus\:border-black:focus { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .lg\:focus\:border-white:focus { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .lg\:focus\:border-gray-100:focus { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .lg\:focus\:border-gray-200:focus { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .lg\:focus\:border-gray-300:focus { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .lg\:focus\:border-gray-400:focus { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .lg\:focus\:border-gray-500:focus { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .lg\:focus\:border-gray-600:focus { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .lg\:focus\:border-gray-700:focus { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .lg\:focus\:border-gray-800:focus { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .lg\:focus\:border-gray-900:focus { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .lg\:focus\:border-red-100:focus { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .lg\:focus\:border-red-200:focus { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .lg\:focus\:border-red-300:focus { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .lg\:focus\:border-red-400:focus { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .lg\:focus\:border-red-500:focus { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .lg\:focus\:border-red-600:focus { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .lg\:focus\:border-red-700:focus { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .lg\:focus\:border-red-800:focus { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .lg\:focus\:border-red-900:focus { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .lg\:focus\:border-orange-100:focus { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .lg\:focus\:border-orange-200:focus { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .lg\:focus\:border-orange-300:focus { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .lg\:focus\:border-orange-400:focus { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .lg\:focus\:border-orange-500:focus { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .lg\:focus\:border-orange-600:focus { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .lg\:focus\:border-orange-700:focus { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .lg\:focus\:border-orange-800:focus { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .lg\:focus\:border-orange-900:focus { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-100:focus { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-200:focus { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-300:focus { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-400:focus { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-500:focus { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-600:focus { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-700:focus { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-800:focus { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .lg\:focus\:border-yellow-900:focus { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .lg\:focus\:border-green-100:focus { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .lg\:focus\:border-green-200:focus { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .lg\:focus\:border-green-300:focus { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .lg\:focus\:border-green-400:focus { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .lg\:focus\:border-green-500:focus { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .lg\:focus\:border-green-600:focus { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .lg\:focus\:border-green-700:focus { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .lg\:focus\:border-green-800:focus { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .lg\:focus\:border-green-900:focus { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .lg\:focus\:border-teal-100:focus { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .lg\:focus\:border-teal-200:focus { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .lg\:focus\:border-teal-300:focus { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .lg\:focus\:border-teal-400:focus { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .lg\:focus\:border-teal-500:focus { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .lg\:focus\:border-teal-600:focus { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .lg\:focus\:border-teal-700:focus { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .lg\:focus\:border-teal-800:focus { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .lg\:focus\:border-teal-900:focus { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .lg\:focus\:border-blue-100:focus { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .lg\:focus\:border-blue-200:focus { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .lg\:focus\:border-blue-300:focus { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .lg\:focus\:border-blue-400:focus { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .lg\:focus\:border-blue-500:focus { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .lg\:focus\:border-blue-600:focus { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .lg\:focus\:border-blue-700:focus { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .lg\:focus\:border-blue-800:focus { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .lg\:focus\:border-blue-900:focus { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-100:focus { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-200:focus { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-300:focus { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-400:focus { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-500:focus { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-600:focus { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-700:focus { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-800:focus { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .lg\:focus\:border-indigo-900:focus { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .lg\:focus\:border-purple-100:focus { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .lg\:focus\:border-purple-200:focus { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .lg\:focus\:border-purple-300:focus { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .lg\:focus\:border-purple-400:focus { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .lg\:focus\:border-purple-500:focus { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .lg\:focus\:border-purple-600:focus { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .lg\:focus\:border-purple-700:focus { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .lg\:focus\:border-purple-800:focus { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .lg\:focus\:border-purple-900:focus { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .lg\:focus\:border-pink-100:focus { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .lg\:focus\:border-pink-200:focus { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .lg\:focus\:border-pink-300:focus { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .lg\:focus\:border-pink-400:focus { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .lg\:focus\:border-pink-500:focus { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .lg\:focus\:border-pink-600:focus { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .lg\:focus\:border-pink-700:focus { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .lg\:focus\:border-pink-800:focus { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .lg\:focus\:border-pink-900:focus { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .lg\:border-opacity-0 { + --border-opacity: 0; + } + + .lg\:border-opacity-25 { + --border-opacity: 0.25; + } + + .lg\:border-opacity-50 { + --border-opacity: 0.5; + } + + .lg\:border-opacity-75 { + --border-opacity: 0.75; + } + + .lg\:border-opacity-100 { + --border-opacity: 1; + } + + .lg\:hover\:border-opacity-0:hover { + --border-opacity: 0; + } + + .lg\:hover\:border-opacity-25:hover { + --border-opacity: 0.25; + } + + .lg\:hover\:border-opacity-50:hover { + --border-opacity: 0.5; + } + + .lg\:hover\:border-opacity-75:hover { + --border-opacity: 0.75; + } + + .lg\:hover\:border-opacity-100:hover { + --border-opacity: 1; + } + + .lg\:focus\:border-opacity-0:focus { + --border-opacity: 0; + } + + .lg\:focus\:border-opacity-25:focus { + --border-opacity: 0.25; + } + + .lg\:focus\:border-opacity-50:focus { + --border-opacity: 0.5; + } + + .lg\:focus\:border-opacity-75:focus { + --border-opacity: 0.75; + } + + .lg\:focus\:border-opacity-100:focus { + --border-opacity: 1; + } + + .lg\:rounded-none { + border-radius: 0; + } + + .lg\:rounded-sm { + border-radius: 0.125rem; + } + + .lg\:rounded { + border-radius: 0.25rem; + } + + .lg\:rounded-md { + border-radius: 0.375rem; + } + + .lg\:rounded-lg { + border-radius: 0.5rem; + } + + .lg\:rounded-full { + border-radius: 9999px; + } + + .lg\:rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .lg\:rounded-r-none { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .lg\:rounded-b-none { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + + .lg\:rounded-l-none { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .lg\:rounded-t-sm { + border-top-left-radius: 0.125rem; + border-top-right-radius: 0.125rem; + } + + .lg\:rounded-r-sm { + border-top-right-radius: 0.125rem; + border-bottom-right-radius: 0.125rem; + } + + .lg\:rounded-b-sm { + border-bottom-right-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .lg\:rounded-l-sm { + border-top-left-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .lg\:rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + + .lg\:rounded-r { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + } + + .lg\:rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .lg\:rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .lg\:rounded-t-md { + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; + } + + .lg\:rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + } + + .lg\:rounded-b-md { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .lg\:rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .lg\:rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } + + .lg\:rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + } + + .lg\:rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .lg\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .lg\:rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px; + } + + .lg\:rounded-r-full { + border-top-right-radius: 9999px; + border-bottom-right-radius: 9999px; + } + + .lg\:rounded-b-full { + border-bottom-right-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .lg\:rounded-l-full { + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .lg\:rounded-tl-none { + border-top-left-radius: 0; + } + + .lg\:rounded-tr-none { + border-top-right-radius: 0; + } + + .lg\:rounded-br-none { + border-bottom-right-radius: 0; + } + + .lg\:rounded-bl-none { + border-bottom-left-radius: 0; + } + + .lg\:rounded-tl-sm { + border-top-left-radius: 0.125rem; + } + + .lg\:rounded-tr-sm { + border-top-right-radius: 0.125rem; + } + + .lg\:rounded-br-sm { + border-bottom-right-radius: 0.125rem; + } + + .lg\:rounded-bl-sm { + border-bottom-left-radius: 0.125rem; + } + + .lg\:rounded-tl { + border-top-left-radius: 0.25rem; + } + + .lg\:rounded-tr { + border-top-right-radius: 0.25rem; + } + + .lg\:rounded-br { + border-bottom-right-radius: 0.25rem; + } + + .lg\:rounded-bl { + border-bottom-left-radius: 0.25rem; + } + + .lg\:rounded-tl-md { + border-top-left-radius: 0.375rem; + } + + .lg\:rounded-tr-md { + border-top-right-radius: 0.375rem; + } + + .lg\:rounded-br-md { + border-bottom-right-radius: 0.375rem; + } + + .lg\:rounded-bl-md { + border-bottom-left-radius: 0.375rem; + } + + .lg\:rounded-tl-lg { + border-top-left-radius: 0.5rem; + } + + .lg\:rounded-tr-lg { + border-top-right-radius: 0.5rem; + } + + .lg\:rounded-br-lg { + border-bottom-right-radius: 0.5rem; + } + + .lg\:rounded-bl-lg { + border-bottom-left-radius: 0.5rem; + } + + .lg\:rounded-tl-full { + border-top-left-radius: 9999px; + } + + .lg\:rounded-tr-full { + border-top-right-radius: 9999px; + } + + .lg\:rounded-br-full { + border-bottom-right-radius: 9999px; + } + + .lg\:rounded-bl-full { + border-bottom-left-radius: 9999px; + } + + .lg\:border-solid { + border-style: solid; + } + + .lg\:border-dashed { + border-style: dashed; + } + + .lg\:border-dotted { + border-style: dotted; + } + + .lg\:border-double { + border-style: double; + } + + .lg\:border-none { + border-style: none; + } + + .lg\:border-0 { + border-width: 0; + } + + .lg\:border-2 { + border-width: 2px; + } + + .lg\:border-4 { + border-width: 4px; + } + + .lg\:border-8 { + border-width: 8px; + } + + .lg\:border { + border-width: 1px; + } + + .lg\:border-t-0 { + border-top-width: 0; + } + + .lg\:border-r-0 { + border-right-width: 0; + } + + .lg\:border-b-0 { + border-bottom-width: 0; + } + + .lg\:border-l-0 { + border-left-width: 0; + } + + .lg\:border-t-2 { + border-top-width: 2px; + } + + .lg\:border-r-2 { + border-right-width: 2px; + } + + .lg\:border-b-2 { + border-bottom-width: 2px; + } + + .lg\:border-l-2 { + border-left-width: 2px; + } + + .lg\:border-t-4 { + border-top-width: 4px; + } + + .lg\:border-r-4 { + border-right-width: 4px; + } + + .lg\:border-b-4 { + border-bottom-width: 4px; + } + + .lg\:border-l-4 { + border-left-width: 4px; + } + + .lg\:border-t-8 { + border-top-width: 8px; + } + + .lg\:border-r-8 { + border-right-width: 8px; + } + + .lg\:border-b-8 { + border-bottom-width: 8px; + } + + .lg\:border-l-8 { + border-left-width: 8px; + } + + .lg\:border-t { + border-top-width: 1px; + } + + .lg\:border-r { + border-right-width: 1px; + } + + .lg\:border-b { + border-bottom-width: 1px; + } + + .lg\:border-l { + border-left-width: 1px; + } + + .lg\:box-border { + box-sizing: border-box; + } + + .lg\:box-content { + box-sizing: content-box; + } + + .lg\:cursor-auto { + cursor: auto; + } + + .lg\:cursor-default { + cursor: default; + } + + .lg\:cursor-pointer { + cursor: pointer; + } + + .lg\:cursor-wait { + cursor: wait; + } + + .lg\:cursor-text { + cursor: text; + } + + .lg\:cursor-move { + cursor: move; + } + + .lg\:cursor-not-allowed { + cursor: not-allowed; + } + + .lg\:block { + display: block; + } + + .lg\:inline-block { + display: inline-block; + } + + .lg\:inline { + display: inline; + } + + .lg\:flex { + display: flex; + } + + .lg\:inline-flex { + display: inline-flex; + } + + .lg\:table { + display: table; + } + + .lg\:table-caption { + display: table-caption; + } + + .lg\:table-cell { + display: table-cell; + } + + .lg\:table-column { + display: table-column; + } + + .lg\:table-column-group { + display: table-column-group; + } + + .lg\:table-footer-group { + display: table-footer-group; + } + + .lg\:table-header-group { + display: table-header-group; + } + + .lg\:table-row-group { + display: table-row-group; + } + + .lg\:table-row { + display: table-row; + } + + .lg\:flow-root { + display: flow-root; + } + + .lg\:grid { + display: grid; + } + + .lg\:inline-grid { + display: inline-grid; + } + + .lg\:contents { + display: contents; + } + + .lg\:hidden { + display: none; + } + + .lg\:flex-row { + flex-direction: row; + } + + .lg\:flex-row-reverse { + flex-direction: row-reverse; + } + + .lg\:flex-col { + flex-direction: column; + } + + .lg\:flex-col-reverse { + flex-direction: column-reverse; + } + + .lg\:flex-wrap { + flex-wrap: wrap; + } + + .lg\:flex-wrap-reverse { + flex-wrap: wrap-reverse; + } + + .lg\:flex-no-wrap { + flex-wrap: nowrap; + } + + .lg\:place-items-auto { + place-items: auto; + } + + .lg\:place-items-start { + place-items: start; + } + + .lg\:place-items-end { + place-items: end; + } + + .lg\:place-items-center { + place-items: center; + } + + .lg\:place-items-stretch { + place-items: stretch; + } + + .lg\:place-content-center { + place-content: center; + } + + .lg\:place-content-start { + place-content: start; + } + + .lg\:place-content-end { + place-content: end; + } + + .lg\:place-content-between { + place-content: space-between; + } + + .lg\:place-content-around { + place-content: space-around; + } + + .lg\:place-content-evenly { + place-content: space-evenly; + } + + .lg\:place-content-stretch { + place-content: stretch; + } + + .lg\:place-self-auto { + place-self: auto; + } + + .lg\:place-self-start { + place-self: start; + } + + .lg\:place-self-end { + place-self: end; + } + + .lg\:place-self-center { + place-self: center; + } + + .lg\:place-self-stretch { + place-self: stretch; + } + + .lg\:items-start { + align-items: flex-start; + } + + .lg\:items-end { + align-items: flex-end; + } + + .lg\:items-center { + align-items: center; + } + + .lg\:items-baseline { + align-items: baseline; + } + + .lg\:items-stretch { + align-items: stretch; + } + + .lg\:content-center { + align-content: center; + } + + .lg\:content-start { + align-content: flex-start; + } + + .lg\:content-end { + align-content: flex-end; + } + + .lg\:content-between { + align-content: space-between; + } + + .lg\:content-around { + align-content: space-around; + } + + .lg\:content-evenly { + align-content: space-evenly; + } + + .lg\:self-auto { + align-self: auto; + } + + .lg\:self-start { + align-self: flex-start; + } + + .lg\:self-end { + align-self: flex-end; + } + + .lg\:self-center { + align-self: center; + } + + .lg\:self-stretch { + align-self: stretch; + } + + .lg\:justify-items-auto { + justify-items: auto; + } + + .lg\:justify-items-start { + justify-items: start; + } + + .lg\:justify-items-end { + justify-items: end; + } + + .lg\:justify-items-center { + justify-items: center; + } + + .lg\:justify-items-stretch { + justify-items: stretch; + } + + .lg\:justify-start { + justify-content: flex-start; + } + + .lg\:justify-end { + justify-content: flex-end; + } + + .lg\:justify-center { + justify-content: center; + } + + .lg\:justify-between { + justify-content: space-between; + } + + .lg\:justify-around { + justify-content: space-around; + } + + .lg\:justify-evenly { + justify-content: space-evenly; + } + + .lg\:justify-self-auto { + justify-self: auto; + } + + .lg\:justify-self-start { + justify-self: start; + } + + .lg\:justify-self-end { + justify-self: end; + } + + .lg\:justify-self-center { + justify-self: center; + } + + .lg\:justify-self-stretch { + justify-self: stretch; + } + + .lg\:flex-1 { + flex: 1 1 0%; + } + + .lg\:flex-auto { + flex: 1 1 auto; + } + + .lg\:flex-initial { + flex: 0 1 auto; + } + + .lg\:flex-none { + flex: none; + } + + .lg\:flex-grow-0 { + flex-grow: 0; + } + + .lg\:flex-grow { + flex-grow: 1; + } + + .lg\:flex-shrink-0 { + flex-shrink: 0; + } + + .lg\:flex-shrink { + flex-shrink: 1; + } + + .lg\:order-1 { + order: 1; + } + + .lg\:order-2 { + order: 2; + } + + .lg\:order-3 { + order: 3; + } + + .lg\:order-4 { + order: 4; + } + + .lg\:order-5 { + order: 5; + } + + .lg\:order-6 { + order: 6; + } + + .lg\:order-7 { + order: 7; + } + + .lg\:order-8 { + order: 8; + } + + .lg\:order-9 { + order: 9; + } + + .lg\:order-10 { + order: 10; + } + + .lg\:order-11 { + order: 11; + } + + .lg\:order-12 { + order: 12; + } + + .lg\:order-first { + order: -9999; + } + + .lg\:order-last { + order: 9999; + } + + .lg\:order-none { + order: 0; + } + + .lg\:float-right { + float: right; + } + + .lg\:float-left { + float: left; + } + + .lg\:float-none { + float: none; + } + + .lg\:clearfix:after { + content: ""; + display: table; + clear: both; + } + + .lg\:clear-left { + clear: left; + } + + .lg\:clear-right { + clear: right; + } + + .lg\:clear-both { + clear: both; + } + + .lg\:clear-none { + clear: none; + } + + .lg\:font-sans { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + } + + .lg\:font-serif { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + } + + .lg\:font-mono { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .lg\:font-hairline { + font-weight: 100; + } + + .lg\:font-thin { + font-weight: 200; + } + + .lg\:font-light { + font-weight: 300; + } + + .lg\:font-normal { + font-weight: 400; + } + + .lg\:font-medium { + font-weight: 500; + } + + .lg\:font-semibold { + font-weight: 600; + } + + .lg\:font-bold { + font-weight: 700; + } + + .lg\:font-extrabold { + font-weight: 800; + } + + .lg\:font-black { + font-weight: 900; + } + + .lg\:hover\:font-hairline:hover { + font-weight: 100; + } + + .lg\:hover\:font-thin:hover { + font-weight: 200; + } + + .lg\:hover\:font-light:hover { + font-weight: 300; + } + + .lg\:hover\:font-normal:hover { + font-weight: 400; + } + + .lg\:hover\:font-medium:hover { + font-weight: 500; + } + + .lg\:hover\:font-semibold:hover { + font-weight: 600; + } + + .lg\:hover\:font-bold:hover { + font-weight: 700; + } + + .lg\:hover\:font-extrabold:hover { + font-weight: 800; + } + + .lg\:hover\:font-black:hover { + font-weight: 900; + } + + .lg\:focus\:font-hairline:focus { + font-weight: 100; + } + + .lg\:focus\:font-thin:focus { + font-weight: 200; + } + + .lg\:focus\:font-light:focus { + font-weight: 300; + } + + .lg\:focus\:font-normal:focus { + font-weight: 400; + } + + .lg\:focus\:font-medium:focus { + font-weight: 500; + } + + .lg\:focus\:font-semibold:focus { + font-weight: 600; + } + + .lg\:focus\:font-bold:focus { + font-weight: 700; + } + + .lg\:focus\:font-extrabold:focus { + font-weight: 800; + } + + .lg\:focus\:font-black:focus { + font-weight: 900; + } + + .lg\:h-0 { + height: 0; + } + + .lg\:h-1 { + height: 0.25rem; + } + + .lg\:h-2 { + height: 0.5rem; + } + + .lg\:h-3 { + height: 0.75rem; + } + + .lg\:h-4 { + height: 1rem; + } + + .lg\:h-5 { + height: 1.25rem; + } + + .lg\:h-6 { + height: 1.5rem; + } + + .lg\:h-8 { + height: 2rem; + } + + .lg\:h-10 { + height: 2.5rem; + } + + .lg\:h-12 { + height: 3rem; + } + + .lg\:h-16 { + height: 4rem; + } + + .lg\:h-20 { + height: 5rem; + } + + .lg\:h-24 { + height: 6rem; + } + + .lg\:h-32 { + height: 8rem; + } + + .lg\:h-40 { + height: 10rem; + } + + .lg\:h-48 { + height: 12rem; + } + + .lg\:h-56 { + height: 14rem; + } + + .lg\:h-64 { + height: 16rem; + } + + .lg\:h-auto { + height: auto; + } + + .lg\:h-px { + height: 1px; + } + + .lg\:h-full { + height: 100%; + } + + .lg\:h-screen { + height: 100vh; + } + + .lg\:text-xs { + font-size: 0.75rem; + } + + .lg\:text-sm { + font-size: 0.875rem; + } + + .lg\:text-base { + font-size: 1rem; + } + + .lg\:text-lg { + font-size: 1.125rem; + } + + .lg\:text-xl { + font-size: 1.25rem; + } + + .lg\:text-2xl { + font-size: 1.5rem; + } + + .lg\:text-3xl { + font-size: 1.875rem; + } + + .lg\:text-4xl { + font-size: 2.25rem; + } + + .lg\:text-5xl { + font-size: 3rem; + } + + .lg\:text-6xl { + font-size: 4rem; + } + + .lg\:leading-3 { + line-height: .75rem; + } + + .lg\:leading-4 { + line-height: 1rem; + } + + .lg\:leading-5 { + line-height: 1.25rem; + } + + .lg\:leading-6 { + line-height: 1.5rem; + } + + .lg\:leading-7 { + line-height: 1.75rem; + } + + .lg\:leading-8 { + line-height: 2rem; + } + + .lg\:leading-9 { + line-height: 2.25rem; + } + + .lg\:leading-10 { + line-height: 2.5rem; + } + + .lg\:leading-none { + line-height: 1; + } + + .lg\:leading-tight { + line-height: 1.25; + } + + .lg\:leading-snug { + line-height: 1.375; + } + + .lg\:leading-normal { + line-height: 1.5; + } + + .lg\:leading-relaxed { + line-height: 1.625; + } + + .lg\:leading-loose { + line-height: 2; + } + + .lg\:list-inside { + list-style-position: inside; + } + + .lg\:list-outside { + list-style-position: outside; + } + + .lg\:list-none { + list-style-type: none; + } + + .lg\:list-disc { + list-style-type: disc; + } + + .lg\:list-decimal { + list-style-type: decimal; + } + + .lg\:m-0 { + margin: 0; + } + + .lg\:m-1 { + margin: 0.25rem; + } + + .lg\:m-2 { + margin: 0.5rem; + } + + .lg\:m-3 { + margin: 0.75rem; + } + + .lg\:m-4 { + margin: 1rem; + } + + .lg\:m-5 { + margin: 1.25rem; + } + + .lg\:m-6 { + margin: 1.5rem; + } + + .lg\:m-8 { + margin: 2rem; + } + + .lg\:m-10 { + margin: 2.5rem; + } + + .lg\:m-12 { + margin: 3rem; + } + + .lg\:m-16 { + margin: 4rem; + } + + .lg\:m-20 { + margin: 5rem; + } + + .lg\:m-24 { + margin: 6rem; + } + + .lg\:m-32 { + margin: 8rem; + } + + .lg\:m-40 { + margin: 10rem; + } + + .lg\:m-48 { + margin: 12rem; + } + + .lg\:m-56 { + margin: 14rem; + } + + .lg\:m-64 { + margin: 16rem; + } + + .lg\:m-auto { + margin: auto; + } + + .lg\:m-px { + margin: 1px; + } + + .lg\:-m-1 { + margin: -0.25rem; + } + + .lg\:-m-2 { + margin: -0.5rem; + } + + .lg\:-m-3 { + margin: -0.75rem; + } + + .lg\:-m-4 { + margin: -1rem; + } + + .lg\:-m-5 { + margin: -1.25rem; + } + + .lg\:-m-6 { + margin: -1.5rem; + } + + .lg\:-m-8 { + margin: -2rem; + } + + .lg\:-m-10 { + margin: -2.5rem; + } + + .lg\:-m-12 { + margin: -3rem; + } + + .lg\:-m-16 { + margin: -4rem; + } + + .lg\:-m-20 { + margin: -5rem; + } + + .lg\:-m-24 { + margin: -6rem; + } + + .lg\:-m-32 { + margin: -8rem; + } + + .lg\:-m-40 { + margin: -10rem; + } + + .lg\:-m-48 { + margin: -12rem; + } + + .lg\:-m-56 { + margin: -14rem; + } + + .lg\:-m-64 { + margin: -16rem; + } + + .lg\:-m-px { + margin: -1px; + } + + .lg\:my-0 { + margin-top: 0; + margin-bottom: 0; + } + + .lg\:mx-0 { + margin-left: 0; + margin-right: 0; + } + + .lg\:my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + } + + .lg\:mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + + .lg\:my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + .lg\:mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + + .lg\:my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + } + + .lg\:mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; + } + + .lg\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + .lg\:mx-4 { + margin-left: 1rem; + margin-right: 1rem; + } + + .lg\:my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + } + + .lg\:mx-5 { + margin-left: 1.25rem; + margin-right: 1.25rem; + } + + .lg\:my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + + .lg\:mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; + } + + .lg\:my-8 { + margin-top: 2rem; + margin-bottom: 2rem; + } + + .lg\:mx-8 { + margin-left: 2rem; + margin-right: 2rem; + } + + .lg\:my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; + } + + .lg\:mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; + } + + .lg\:my-12 { + margin-top: 3rem; + margin-bottom: 3rem; + } + + .lg\:mx-12 { + margin-left: 3rem; + margin-right: 3rem; + } + + .lg\:my-16 { + margin-top: 4rem; + margin-bottom: 4rem; + } + + .lg\:mx-16 { + margin-left: 4rem; + margin-right: 4rem; + } + + .lg\:my-20 { + margin-top: 5rem; + margin-bottom: 5rem; + } + + .lg\:mx-20 { + margin-left: 5rem; + margin-right: 5rem; + } + + .lg\:my-24 { + margin-top: 6rem; + margin-bottom: 6rem; + } + + .lg\:mx-24 { + margin-left: 6rem; + margin-right: 6rem; + } + + .lg\:my-32 { + margin-top: 8rem; + margin-bottom: 8rem; + } + + .lg\:mx-32 { + margin-left: 8rem; + margin-right: 8rem; + } + + .lg\:my-40 { + margin-top: 10rem; + margin-bottom: 10rem; + } + + .lg\:mx-40 { + margin-left: 10rem; + margin-right: 10rem; + } + + .lg\:my-48 { + margin-top: 12rem; + margin-bottom: 12rem; + } + + .lg\:mx-48 { + margin-left: 12rem; + margin-right: 12rem; + } + + .lg\:my-56 { + margin-top: 14rem; + margin-bottom: 14rem; + } + + .lg\:mx-56 { + margin-left: 14rem; + margin-right: 14rem; + } + + .lg\:my-64 { + margin-top: 16rem; + margin-bottom: 16rem; + } + + .lg\:mx-64 { + margin-left: 16rem; + margin-right: 16rem; + } + + .lg\:my-auto { + margin-top: auto; + margin-bottom: auto; + } + + .lg\:mx-auto { + margin-left: auto; + margin-right: auto; + } + + .lg\:my-px { + margin-top: 1px; + margin-bottom: 1px; + } + + .lg\:mx-px { + margin-left: 1px; + margin-right: 1px; + } + + .lg\:-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; + } + + .lg\:-mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; + } + + .lg\:-my-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; + } + + .lg\:-mx-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; + } + + .lg\:-my-3 { + margin-top: -0.75rem; + margin-bottom: -0.75rem; + } + + .lg\:-mx-3 { + margin-left: -0.75rem; + margin-right: -0.75rem; + } + + .lg\:-my-4 { + margin-top: -1rem; + margin-bottom: -1rem; + } + + .lg\:-mx-4 { + margin-left: -1rem; + margin-right: -1rem; + } + + .lg\:-my-5 { + margin-top: -1.25rem; + margin-bottom: -1.25rem; + } + + .lg\:-mx-5 { + margin-left: -1.25rem; + margin-right: -1.25rem; + } + + .lg\:-my-6 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; + } + + .lg\:-mx-6 { + margin-left: -1.5rem; + margin-right: -1.5rem; + } + + .lg\:-my-8 { + margin-top: -2rem; + margin-bottom: -2rem; + } + + .lg\:-mx-8 { + margin-left: -2rem; + margin-right: -2rem; + } + + .lg\:-my-10 { + margin-top: -2.5rem; + margin-bottom: -2.5rem; + } + + .lg\:-mx-10 { + margin-left: -2.5rem; + margin-right: -2.5rem; + } + + .lg\:-my-12 { + margin-top: -3rem; + margin-bottom: -3rem; + } + + .lg\:-mx-12 { + margin-left: -3rem; + margin-right: -3rem; + } + + .lg\:-my-16 { + margin-top: -4rem; + margin-bottom: -4rem; + } + + .lg\:-mx-16 { + margin-left: -4rem; + margin-right: -4rem; + } + + .lg\:-my-20 { + margin-top: -5rem; + margin-bottom: -5rem; + } + + .lg\:-mx-20 { + margin-left: -5rem; + margin-right: -5rem; + } + + .lg\:-my-24 { + margin-top: -6rem; + margin-bottom: -6rem; + } + + .lg\:-mx-24 { + margin-left: -6rem; + margin-right: -6rem; + } + + .lg\:-my-32 { + margin-top: -8rem; + margin-bottom: -8rem; + } + + .lg\:-mx-32 { + margin-left: -8rem; + margin-right: -8rem; + } + + .lg\:-my-40 { + margin-top: -10rem; + margin-bottom: -10rem; + } + + .lg\:-mx-40 { + margin-left: -10rem; + margin-right: -10rem; + } + + .lg\:-my-48 { + margin-top: -12rem; + margin-bottom: -12rem; + } + + .lg\:-mx-48 { + margin-left: -12rem; + margin-right: -12rem; + } + + .lg\:-my-56 { + margin-top: -14rem; + margin-bottom: -14rem; + } + + .lg\:-mx-56 { + margin-left: -14rem; + margin-right: -14rem; + } + + .lg\:-my-64 { + margin-top: -16rem; + margin-bottom: -16rem; + } + + .lg\:-mx-64 { + margin-left: -16rem; + margin-right: -16rem; + } + + .lg\:-my-px { + margin-top: -1px; + margin-bottom: -1px; + } + + .lg\:-mx-px { + margin-left: -1px; + margin-right: -1px; + } + + .lg\:mt-0 { + margin-top: 0; + } + + .lg\:mr-0 { + margin-right: 0; + } + + .lg\:mb-0 { + margin-bottom: 0; + } + + .lg\:ml-0 { + margin-left: 0; + } + + .lg\:mt-1 { + margin-top: 0.25rem; + } + + .lg\:mr-1 { + margin-right: 0.25rem; + } + + .lg\:mb-1 { + margin-bottom: 0.25rem; + } + + .lg\:ml-1 { + margin-left: 0.25rem; + } + + .lg\:mt-2 { + margin-top: 0.5rem; + } + + .lg\:mr-2 { + margin-right: 0.5rem; + } + + .lg\:mb-2 { + margin-bottom: 0.5rem; + } + + .lg\:ml-2 { + margin-left: 0.5rem; + } + + .lg\:mt-3 { + margin-top: 0.75rem; + } + + .lg\:mr-3 { + margin-right: 0.75rem; + } + + .lg\:mb-3 { + margin-bottom: 0.75rem; + } + + .lg\:ml-3 { + margin-left: 0.75rem; + } + + .lg\:mt-4 { + margin-top: 1rem; + } + + .lg\:mr-4 { + margin-right: 1rem; + } + + .lg\:mb-4 { + margin-bottom: 1rem; + } + + .lg\:ml-4 { + margin-left: 1rem; + } + + .lg\:mt-5 { + margin-top: 1.25rem; + } + + .lg\:mr-5 { + margin-right: 1.25rem; + } + + .lg\:mb-5 { + margin-bottom: 1.25rem; + } + + .lg\:ml-5 { + margin-left: 1.25rem; + } + + .lg\:mt-6 { + margin-top: 1.5rem; + } + + .lg\:mr-6 { + margin-right: 1.5rem; + } + + .lg\:mb-6 { + margin-bottom: 1.5rem; + } + + .lg\:ml-6 { + margin-left: 1.5rem; + } + + .lg\:mt-8 { + margin-top: 2rem; + } + + .lg\:mr-8 { + margin-right: 2rem; + } + + .lg\:mb-8 { + margin-bottom: 2rem; + } + + .lg\:ml-8 { + margin-left: 2rem; + } + + .lg\:mt-10 { + margin-top: 2.5rem; + } + + .lg\:mr-10 { + margin-right: 2.5rem; + } + + .lg\:mb-10 { + margin-bottom: 2.5rem; + } + + .lg\:ml-10 { + margin-left: 2.5rem; + } + + .lg\:mt-12 { + margin-top: 3rem; + } + + .lg\:mr-12 { + margin-right: 3rem; + } + + .lg\:mb-12 { + margin-bottom: 3rem; + } + + .lg\:ml-12 { + margin-left: 3rem; + } + + .lg\:mt-16 { + margin-top: 4rem; + } + + .lg\:mr-16 { + margin-right: 4rem; + } + + .lg\:mb-16 { + margin-bottom: 4rem; + } + + .lg\:ml-16 { + margin-left: 4rem; + } + + .lg\:mt-20 { + margin-top: 5rem; + } + + .lg\:mr-20 { + margin-right: 5rem; + } + + .lg\:mb-20 { + margin-bottom: 5rem; + } + + .lg\:ml-20 { + margin-left: 5rem; + } + + .lg\:mt-24 { + margin-top: 6rem; + } + + .lg\:mr-24 { + margin-right: 6rem; + } + + .lg\:mb-24 { + margin-bottom: 6rem; + } + + .lg\:ml-24 { + margin-left: 6rem; + } + + .lg\:mt-32 { + margin-top: 8rem; + } + + .lg\:mr-32 { + margin-right: 8rem; + } + + .lg\:mb-32 { + margin-bottom: 8rem; + } + + .lg\:ml-32 { + margin-left: 8rem; + } + + .lg\:mt-40 { + margin-top: 10rem; + } + + .lg\:mr-40 { + margin-right: 10rem; + } + + .lg\:mb-40 { + margin-bottom: 10rem; + } + + .lg\:ml-40 { + margin-left: 10rem; + } + + .lg\:mt-48 { + margin-top: 12rem; + } + + .lg\:mr-48 { + margin-right: 12rem; + } + + .lg\:mb-48 { + margin-bottom: 12rem; + } + + .lg\:ml-48 { + margin-left: 12rem; + } + + .lg\:mt-56 { + margin-top: 14rem; + } + + .lg\:mr-56 { + margin-right: 14rem; + } + + .lg\:mb-56 { + margin-bottom: 14rem; + } + + .lg\:ml-56 { + margin-left: 14rem; + } + + .lg\:mt-64 { + margin-top: 16rem; + } + + .lg\:mr-64 { + margin-right: 16rem; + } + + .lg\:mb-64 { + margin-bottom: 16rem; + } + + .lg\:ml-64 { + margin-left: 16rem; + } + + .lg\:mt-auto { + margin-top: auto; + } + + .lg\:mr-auto { + margin-right: auto; + } + + .lg\:mb-auto { + margin-bottom: auto; + } + + .lg\:ml-auto { + margin-left: auto; + } + + .lg\:mt-px { + margin-top: 1px; + } + + .lg\:mr-px { + margin-right: 1px; + } + + .lg\:mb-px { + margin-bottom: 1px; + } + + .lg\:ml-px { + margin-left: 1px; + } + + .lg\:-mt-1 { + margin-top: -0.25rem; + } + + .lg\:-mr-1 { + margin-right: -0.25rem; + } + + .lg\:-mb-1 { + margin-bottom: -0.25rem; + } + + .lg\:-ml-1 { + margin-left: -0.25rem; + } + + .lg\:-mt-2 { + margin-top: -0.5rem; + } + + .lg\:-mr-2 { + margin-right: -0.5rem; + } + + .lg\:-mb-2 { + margin-bottom: -0.5rem; + } + + .lg\:-ml-2 { + margin-left: -0.5rem; + } + + .lg\:-mt-3 { + margin-top: -0.75rem; + } + + .lg\:-mr-3 { + margin-right: -0.75rem; + } + + .lg\:-mb-3 { + margin-bottom: -0.75rem; + } + + .lg\:-ml-3 { + margin-left: -0.75rem; + } + + .lg\:-mt-4 { + margin-top: -1rem; + } + + .lg\:-mr-4 { + margin-right: -1rem; + } + + .lg\:-mb-4 { + margin-bottom: -1rem; + } + + .lg\:-ml-4 { + margin-left: -1rem; + } + + .lg\:-mt-5 { + margin-top: -1.25rem; + } + + .lg\:-mr-5 { + margin-right: -1.25rem; + } + + .lg\:-mb-5 { + margin-bottom: -1.25rem; + } + + .lg\:-ml-5 { + margin-left: -1.25rem; + } + + .lg\:-mt-6 { + margin-top: -1.5rem; + } + + .lg\:-mr-6 { + margin-right: -1.5rem; + } + + .lg\:-mb-6 { + margin-bottom: -1.5rem; + } + + .lg\:-ml-6 { + margin-left: -1.5rem; + } + + .lg\:-mt-8 { + margin-top: -2rem; + } + + .lg\:-mr-8 { + margin-right: -2rem; + } + + .lg\:-mb-8 { + margin-bottom: -2rem; + } + + .lg\:-ml-8 { + margin-left: -2rem; + } + + .lg\:-mt-10 { + margin-top: -2.5rem; + } + + .lg\:-mr-10 { + margin-right: -2.5rem; + } + + .lg\:-mb-10 { + margin-bottom: -2.5rem; + } + + .lg\:-ml-10 { + margin-left: -2.5rem; + } + + .lg\:-mt-12 { + margin-top: -3rem; + } + + .lg\:-mr-12 { + margin-right: -3rem; + } + + .lg\:-mb-12 { + margin-bottom: -3rem; + } + + .lg\:-ml-12 { + margin-left: -3rem; + } + + .lg\:-mt-16 { + margin-top: -4rem; + } + + .lg\:-mr-16 { + margin-right: -4rem; + } + + .lg\:-mb-16 { + margin-bottom: -4rem; + } + + .lg\:-ml-16 { + margin-left: -4rem; + } + + .lg\:-mt-20 { + margin-top: -5rem; + } + + .lg\:-mr-20 { + margin-right: -5rem; + } + + .lg\:-mb-20 { + margin-bottom: -5rem; + } + + .lg\:-ml-20 { + margin-left: -5rem; + } + + .lg\:-mt-24 { + margin-top: -6rem; + } + + .lg\:-mr-24 { + margin-right: -6rem; + } + + .lg\:-mb-24 { + margin-bottom: -6rem; + } + + .lg\:-ml-24 { + margin-left: -6rem; + } + + .lg\:-mt-32 { + margin-top: -8rem; + } + + .lg\:-mr-32 { + margin-right: -8rem; + } + + .lg\:-mb-32 { + margin-bottom: -8rem; + } + + .lg\:-ml-32 { + margin-left: -8rem; + } + + .lg\:-mt-40 { + margin-top: -10rem; + } + + .lg\:-mr-40 { + margin-right: -10rem; + } + + .lg\:-mb-40 { + margin-bottom: -10rem; + } + + .lg\:-ml-40 { + margin-left: -10rem; + } + + .lg\:-mt-48 { + margin-top: -12rem; + } + + .lg\:-mr-48 { + margin-right: -12rem; + } + + .lg\:-mb-48 { + margin-bottom: -12rem; + } + + .lg\:-ml-48 { + margin-left: -12rem; + } + + .lg\:-mt-56 { + margin-top: -14rem; + } + + .lg\:-mr-56 { + margin-right: -14rem; + } + + .lg\:-mb-56 { + margin-bottom: -14rem; + } + + .lg\:-ml-56 { + margin-left: -14rem; + } + + .lg\:-mt-64 { + margin-top: -16rem; + } + + .lg\:-mr-64 { + margin-right: -16rem; + } + + .lg\:-mb-64 { + margin-bottom: -16rem; + } + + .lg\:-ml-64 { + margin-left: -16rem; + } + + .lg\:-mt-px { + margin-top: -1px; + } + + .lg\:-mr-px { + margin-right: -1px; + } + + .lg\:-mb-px { + margin-bottom: -1px; + } + + .lg\:-ml-px { + margin-left: -1px; + } + + .lg\:max-h-full { + max-height: 100%; + } + + .lg\:max-h-screen { + max-height: 100vh; + } + + .lg\:max-w-none { + max-width: none; + } + + .lg\:max-w-xs { + max-width: 20rem; + } + + .lg\:max-w-sm { + max-width: 24rem; + } + + .lg\:max-w-md { + max-width: 28rem; + } + + .lg\:max-w-lg { + max-width: 32rem; + } + + .lg\:max-w-xl { + max-width: 36rem; + } + + .lg\:max-w-2xl { + max-width: 42rem; + } + + .lg\:max-w-3xl { + max-width: 48rem; + } + + .lg\:max-w-4xl { + max-width: 56rem; + } + + .lg\:max-w-5xl { + max-width: 64rem; + } + + .lg\:max-w-6xl { + max-width: 72rem; + } + + .lg\:max-w-full { + max-width: 100%; + } + + .lg\:max-w-screen-sm { + max-width: 640px; + } + + .lg\:max-w-screen-md { + max-width: 768px; + } + + .lg\:max-w-screen-lg { + max-width: 1024px; + } + + .lg\:max-w-screen-xl { + max-width: 1280px; + } + + .lg\:min-h-0 { + min-height: 0; + } + + .lg\:min-h-full { + min-height: 100%; + } + + .lg\:min-h-screen { + min-height: 100vh; + } + + .lg\:min-w-0 { + min-width: 0; + } + + .lg\:min-w-full { + min-width: 100%; + } + + .lg\:object-contain { + -o-object-fit: contain; + object-fit: contain; + } + + .lg\:object-cover { + -o-object-fit: cover; + object-fit: cover; + } + + .lg\:object-fill { + -o-object-fit: fill; + object-fit: fill; + } + + .lg\:object-none { + -o-object-fit: none; + object-fit: none; + } + + .lg\:object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; + } + + .lg\:object-bottom { + -o-object-position: bottom; + object-position: bottom; + } + + .lg\:object-center { + -o-object-position: center; + object-position: center; + } + + .lg\:object-left { + -o-object-position: left; + object-position: left; + } + + .lg\:object-left-bottom { + -o-object-position: left bottom; + object-position: left bottom; + } + + .lg\:object-left-top { + -o-object-position: left top; + object-position: left top; + } + + .lg\:object-right { + -o-object-position: right; + object-position: right; + } + + .lg\:object-right-bottom { + -o-object-position: right bottom; + object-position: right bottom; + } + + .lg\:object-right-top { + -o-object-position: right top; + object-position: right top; + } + + .lg\:object-top { + -o-object-position: top; + object-position: top; + } + + .lg\:opacity-0 { + opacity: 0; + } + + .lg\:opacity-25 { + opacity: 0.25; + } + + .lg\:opacity-50 { + opacity: 0.5; + } + + .lg\:opacity-75 { + opacity: 0.75; + } + + .lg\:opacity-100 { + opacity: 1; + } + + .lg\:hover\:opacity-0:hover { + opacity: 0; + } + + .lg\:hover\:opacity-25:hover { + opacity: 0.25; + } + + .lg\:hover\:opacity-50:hover { + opacity: 0.5; + } + + .lg\:hover\:opacity-75:hover { + opacity: 0.75; + } + + .lg\:hover\:opacity-100:hover { + opacity: 1; + } + + .lg\:focus\:opacity-0:focus { + opacity: 0; + } + + .lg\:focus\:opacity-25:focus { + opacity: 0.25; + } + + .lg\:focus\:opacity-50:focus { + opacity: 0.5; + } + + .lg\:focus\:opacity-75:focus { + opacity: 0.75; + } + + .lg\:focus\:opacity-100:focus { + opacity: 1; + } + + .lg\:outline-none { + outline: 0; + } + + .lg\:focus\:outline-none:focus { + outline: 0; + } + + .lg\:overflow-auto { + overflow: auto; + } + + .lg\:overflow-hidden { + overflow: hidden; + } + + .lg\:overflow-visible { + overflow: visible; + } + + .lg\:overflow-scroll { + overflow: scroll; + } + + .lg\:overflow-x-auto { + overflow-x: auto; + } + + .lg\:overflow-y-auto { + overflow-y: auto; + } + + .lg\:overflow-x-hidden { + overflow-x: hidden; + } + + .lg\:overflow-y-hidden { + overflow-y: hidden; + } + + .lg\:overflow-x-visible { + overflow-x: visible; + } + + .lg\:overflow-y-visible { + overflow-y: visible; + } + + .lg\:overflow-x-scroll { + overflow-x: scroll; + } + + .lg\:overflow-y-scroll { + overflow-y: scroll; + } + + .lg\:scrolling-touch { + -webkit-overflow-scrolling: touch; + } + + .lg\:scrolling-auto { + -webkit-overflow-scrolling: auto; + } + + .lg\:overscroll-auto { + -ms-scroll-chaining: chained; + overscroll-behavior: auto; + } + + .lg\:overscroll-contain { + -ms-scroll-chaining: none; + overscroll-behavior: contain; + } + + .lg\:overscroll-none { + -ms-scroll-chaining: none; + overscroll-behavior: none; + } + + .lg\:overscroll-y-auto { + overscroll-behavior-y: auto; + } + + .lg\:overscroll-y-contain { + overscroll-behavior-y: contain; + } + + .lg\:overscroll-y-none { + overscroll-behavior-y: none; + } + + .lg\:overscroll-x-auto { + overscroll-behavior-x: auto; + } + + .lg\:overscroll-x-contain { + overscroll-behavior-x: contain; + } + + .lg\:overscroll-x-none { + overscroll-behavior-x: none; + } + + .lg\:p-0 { + padding: 0; + } + + .lg\:p-1 { + padding: 0.25rem; + } + + .lg\:p-2 { + padding: 0.5rem; + } + + .lg\:p-3 { + padding: 0.75rem; + } + + .lg\:p-4 { + padding: 1rem; + } + + .lg\:p-5 { + padding: 1.25rem; + } + + .lg\:p-6 { + padding: 1.5rem; + } + + .lg\:p-8 { + padding: 2rem; + } + + .lg\:p-10 { + padding: 2.5rem; + } + + .lg\:p-12 { + padding: 3rem; + } + + .lg\:p-16 { + padding: 4rem; + } + + .lg\:p-20 { + padding: 5rem; + } + + .lg\:p-24 { + padding: 6rem; + } + + .lg\:p-32 { + padding: 8rem; + } + + .lg\:p-40 { + padding: 10rem; + } + + .lg\:p-48 { + padding: 12rem; + } + + .lg\:p-56 { + padding: 14rem; + } + + .lg\:p-64 { + padding: 16rem; + } + + .lg\:p-px { + padding: 1px; + } + + .lg\:py-0 { + padding-top: 0; + padding-bottom: 0; + } + + .lg\:px-0 { + padding-left: 0; + padding-right: 0; + } + + .lg\:py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } + + .lg\:px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + .lg\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + .lg\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .lg\:py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + + .lg\:px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + + .lg\:py-4 { + padding-top: 1rem; + padding-bottom: 1rem; + } + + .lg\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .lg\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + + .lg\:px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; + } + + .lg\:py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; + } + + .lg\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .lg\:py-8 { + padding-top: 2rem; + padding-bottom: 2rem; + } + + .lg\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .lg\:py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; + } + + .lg\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .lg\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + + .lg\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .lg\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .lg\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .lg\:py-20 { + padding-top: 5rem; + padding-bottom: 5rem; + } + + .lg\:px-20 { + padding-left: 5rem; + padding-right: 5rem; + } + + .lg\:py-24 { + padding-top: 6rem; + padding-bottom: 6rem; + } + + .lg\:px-24 { + padding-left: 6rem; + padding-right: 6rem; + } + + .lg\:py-32 { + padding-top: 8rem; + padding-bottom: 8rem; + } + + .lg\:px-32 { + padding-left: 8rem; + padding-right: 8rem; + } + + .lg\:py-40 { + padding-top: 10rem; + padding-bottom: 10rem; + } + + .lg\:px-40 { + padding-left: 10rem; + padding-right: 10rem; + } + + .lg\:py-48 { + padding-top: 12rem; + padding-bottom: 12rem; + } + + .lg\:px-48 { + padding-left: 12rem; + padding-right: 12rem; + } + + .lg\:py-56 { + padding-top: 14rem; + padding-bottom: 14rem; + } + + .lg\:px-56 { + padding-left: 14rem; + padding-right: 14rem; + } + + .lg\:py-64 { + padding-top: 16rem; + padding-bottom: 16rem; + } + + .lg\:px-64 { + padding-left: 16rem; + padding-right: 16rem; + } + + .lg\:py-px { + padding-top: 1px; + padding-bottom: 1px; + } + + .lg\:px-px { + padding-left: 1px; + padding-right: 1px; + } + + .lg\:pt-0 { + padding-top: 0; + } + + .lg\:pr-0 { + padding-right: 0; + } + + .lg\:pb-0 { + padding-bottom: 0; + } + + .lg\:pl-0 { + padding-left: 0; + } + + .lg\:pt-1 { + padding-top: 0.25rem; + } + + .lg\:pr-1 { + padding-right: 0.25rem; + } + + .lg\:pb-1 { + padding-bottom: 0.25rem; + } + + .lg\:pl-1 { + padding-left: 0.25rem; + } + + .lg\:pt-2 { + padding-top: 0.5rem; + } + + .lg\:pr-2 { + padding-right: 0.5rem; + } + + .lg\:pb-2 { + padding-bottom: 0.5rem; + } + + .lg\:pl-2 { + padding-left: 0.5rem; + } + + .lg\:pt-3 { + padding-top: 0.75rem; + } + + .lg\:pr-3 { + padding-right: 0.75rem; + } + + .lg\:pb-3 { + padding-bottom: 0.75rem; + } + + .lg\:pl-3 { + padding-left: 0.75rem; + } + + .lg\:pt-4 { + padding-top: 1rem; + } + + .lg\:pr-4 { + padding-right: 1rem; + } + + .lg\:pb-4 { + padding-bottom: 1rem; + } + + .lg\:pl-4 { + padding-left: 1rem; + } + + .lg\:pt-5 { + padding-top: 1.25rem; + } + + .lg\:pr-5 { + padding-right: 1.25rem; + } + + .lg\:pb-5 { + padding-bottom: 1.25rem; + } + + .lg\:pl-5 { + padding-left: 1.25rem; + } + + .lg\:pt-6 { + padding-top: 1.5rem; + } + + .lg\:pr-6 { + padding-right: 1.5rem; + } + + .lg\:pb-6 { + padding-bottom: 1.5rem; + } + + .lg\:pl-6 { + padding-left: 1.5rem; + } + + .lg\:pt-8 { + padding-top: 2rem; + } + + .lg\:pr-8 { + padding-right: 2rem; + } + + .lg\:pb-8 { + padding-bottom: 2rem; + } + + .lg\:pl-8 { + padding-left: 2rem; + } + + .lg\:pt-10 { + padding-top: 2.5rem; + } + + .lg\:pr-10 { + padding-right: 2.5rem; + } + + .lg\:pb-10 { + padding-bottom: 2.5rem; + } + + .lg\:pl-10 { + padding-left: 2.5rem; + } + + .lg\:pt-12 { + padding-top: 3rem; + } + + .lg\:pr-12 { + padding-right: 3rem; + } + + .lg\:pb-12 { + padding-bottom: 3rem; + } + + .lg\:pl-12 { + padding-left: 3rem; + } + + .lg\:pt-16 { + padding-top: 4rem; + } + + .lg\:pr-16 { + padding-right: 4rem; + } + + .lg\:pb-16 { + padding-bottom: 4rem; + } + + .lg\:pl-16 { + padding-left: 4rem; + } + + .lg\:pt-20 { + padding-top: 5rem; + } + + .lg\:pr-20 { + padding-right: 5rem; + } + + .lg\:pb-20 { + padding-bottom: 5rem; + } + + .lg\:pl-20 { + padding-left: 5rem; + } + + .lg\:pt-24 { + padding-top: 6rem; + } + + .lg\:pr-24 { + padding-right: 6rem; + } + + .lg\:pb-24 { + padding-bottom: 6rem; + } + + .lg\:pl-24 { + padding-left: 6rem; + } + + .lg\:pt-32 { + padding-top: 8rem; + } + + .lg\:pr-32 { + padding-right: 8rem; + } + + .lg\:pb-32 { + padding-bottom: 8rem; + } + + .lg\:pl-32 { + padding-left: 8rem; + } + + .lg\:pt-40 { + padding-top: 10rem; + } + + .lg\:pr-40 { + padding-right: 10rem; + } + + .lg\:pb-40 { + padding-bottom: 10rem; + } + + .lg\:pl-40 { + padding-left: 10rem; + } + + .lg\:pt-48 { + padding-top: 12rem; + } + + .lg\:pr-48 { + padding-right: 12rem; + } + + .lg\:pb-48 { + padding-bottom: 12rem; + } + + .lg\:pl-48 { + padding-left: 12rem; + } + + .lg\:pt-56 { + padding-top: 14rem; + } + + .lg\:pr-56 { + padding-right: 14rem; + } + + .lg\:pb-56 { + padding-bottom: 14rem; + } + + .lg\:pl-56 { + padding-left: 14rem; + } + + .lg\:pt-64 { + padding-top: 16rem; + } + + .lg\:pr-64 { + padding-right: 16rem; + } + + .lg\:pb-64 { + padding-bottom: 16rem; + } + + .lg\:pl-64 { + padding-left: 16rem; + } + + .lg\:pt-px { + padding-top: 1px; + } + + .lg\:pr-px { + padding-right: 1px; + } + + .lg\:pb-px { + padding-bottom: 1px; + } + + .lg\:pl-px { + padding-left: 1px; + } + + .lg\:placeholder-transparent::-moz-placeholder { + color: transparent; + } + + .lg\:placeholder-transparent:-ms-input-placeholder { + color: transparent; + } + + .lg\:placeholder-transparent::placeholder { + color: transparent; + } + + .lg\:placeholder-current::-moz-placeholder { + color: currentColor; + } + + .lg\:placeholder-current:-ms-input-placeholder { + color: currentColor; + } + + .lg\:placeholder-current::placeholder { + color: currentColor; + } + + .lg\:placeholder-black::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:placeholder-black:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:placeholder-black::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:placeholder-white::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-white:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-white::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-100::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-200::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-300::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-500::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-600::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-700::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-800::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-gray-900::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-200::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-300::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-400::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-500::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-600::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-700::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-800::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:placeholder-red-900::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-100::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-200::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-300::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-400::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-500::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-600::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-700::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-800::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:placeholder-orange-900::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-100::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-200::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-300::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-400::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-500::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-600::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-700::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-800::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:placeholder-yellow-900::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-100::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-200::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-300::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-400::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-500::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-600::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-700::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-800::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:placeholder-green-900::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-100::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-200::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-300::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-400::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-500::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-600::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-700::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-800::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:placeholder-teal-900::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-100::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-200::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-300::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-400::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-500::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-600::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-700::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-800::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-blue-900::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-100::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-200::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-300::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-400::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-500::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-600::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-700::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-800::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:placeholder-indigo-900::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-100::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-200::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-300::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-400::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-500::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-600::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-700::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-800::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:placeholder-purple-900::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-200::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-300::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-400::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-500::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-600::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-700::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-800::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:placeholder-pink-900::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-transparent:focus::-moz-placeholder { + color: transparent; + } + + .lg\:focus\:placeholder-transparent:focus:-ms-input-placeholder { + color: transparent; + } + + .lg\:focus\:placeholder-transparent:focus::placeholder { + color: transparent; + } + + .lg\:focus\:placeholder-current:focus::-moz-placeholder { + color: currentColor; + } + + .lg\:focus\:placeholder-current:focus:-ms-input-placeholder { + color: currentColor; + } + + .lg\:focus\:placeholder-current:focus::placeholder { + color: currentColor; + } + + .lg\:focus\:placeholder-black:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-black:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-black:focus::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-white:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-white:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-white:focus::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-200:focus::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-300:focus::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-400:focus::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-500:focus::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-600:focus::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-gray-900:focus::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-300:focus::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-400:focus::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-500:focus::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-600:focus::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-red-900:focus::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-200:focus::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-600:focus::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-orange-900:focus::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-300:focus::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-800:focus::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-yellow-900:focus::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-300:focus::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-400:focus::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-500:focus::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-600:focus::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-800:focus::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-green-900:focus::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-100:focus::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-200:focus::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-300:focus::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-400:focus::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-500:focus::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-600:focus::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-800:focus::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-teal-900:focus::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-200:focus::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-300:focus::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-400:focus::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-500:focus::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-600:focus::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-blue-900:focus::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-300:focus::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-400:focus::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-500:focus::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-600:focus::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-800:focus::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-indigo-900:focus::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-100:focus::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-200:focus::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-300:focus::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-400:focus::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-500:focus::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-600:focus::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-700:focus::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-800:focus::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-purple-900:focus::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-800:focus::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:focus\:placeholder-pink-900:focus::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .lg\:placeholder-opacity-0::-moz-placeholder { + --placeholder-opacity: 0; + } + + .lg\:placeholder-opacity-0:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .lg\:placeholder-opacity-0::placeholder { + --placeholder-opacity: 0; + } + + .lg\:placeholder-opacity-25::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:placeholder-opacity-25:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:placeholder-opacity-25::placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:placeholder-opacity-50::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:placeholder-opacity-50:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:placeholder-opacity-50::placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:placeholder-opacity-75::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:placeholder-opacity-75:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:placeholder-opacity-75::placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:placeholder-opacity-100::-moz-placeholder { + --placeholder-opacity: 1; + } + + .lg\:placeholder-opacity-100:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .lg\:placeholder-opacity-100::placeholder { + --placeholder-opacity: 1; + } + + .lg\:focus\:placeholder-opacity-0:focus::-moz-placeholder { + --placeholder-opacity: 0; + } + + .lg\:focus\:placeholder-opacity-0:focus:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .lg\:focus\:placeholder-opacity-0:focus::placeholder { + --placeholder-opacity: 0; + } + + .lg\:focus\:placeholder-opacity-25:focus::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:focus\:placeholder-opacity-25:focus:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:focus\:placeholder-opacity-25:focus::placeholder { + --placeholder-opacity: 0.25; + } + + .lg\:focus\:placeholder-opacity-50:focus::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:focus\:placeholder-opacity-50:focus:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:focus\:placeholder-opacity-50:focus::placeholder { + --placeholder-opacity: 0.5; + } + + .lg\:focus\:placeholder-opacity-75:focus::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:focus\:placeholder-opacity-75:focus:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:focus\:placeholder-opacity-75:focus::placeholder { + --placeholder-opacity: 0.75; + } + + .lg\:focus\:placeholder-opacity-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + } + + .lg\:focus\:placeholder-opacity-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .lg\:focus\:placeholder-opacity-100:focus::placeholder { + --placeholder-opacity: 1; + } + + .lg\:pointer-events-none { + pointer-events: none; + } + + .lg\:pointer-events-auto { + pointer-events: auto; + } + + .lg\:static { + position: static; + } + + .lg\:fixed { + position: fixed; + } + + .lg\:absolute { + position: absolute; + } + + .lg\:relative { + position: relative; + } + + .lg\:sticky { + position: -webkit-sticky; + position: sticky; + } + + .lg\:inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .lg\:inset-auto { + top: auto; + right: auto; + bottom: auto; + left: auto; + } + + .lg\:inset-y-0 { + top: 0; + bottom: 0; + } + + .lg\:inset-x-0 { + right: 0; + left: 0; + } + + .lg\:inset-y-auto { + top: auto; + bottom: auto; + } + + .lg\:inset-x-auto { + right: auto; + left: auto; + } + + .lg\:top-0 { + top: 0; + } + + .lg\:right-0 { + right: 0; + } + + .lg\:bottom-0 { + bottom: 0; + } + + .lg\:left-0 { + left: 0; + } + + .lg\:top-auto { + top: auto; + } + + .lg\:right-auto { + right: auto; + } + + .lg\:bottom-auto { + bottom: auto; + } + + .lg\:left-auto { + left: auto; + } + + .lg\:resize-none { + resize: none; + } + + .lg\:resize-y { + resize: vertical; + } + + .lg\:resize-x { + resize: horizontal; + } + + .lg\:resize { + resize: both; + } + + .lg\:shadow-xs { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .lg\:shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .lg\:shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .lg\:shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .lg\:shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .lg\:shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .lg\:shadow-inner { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:shadow-outline { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .lg\:shadow-none { + box-shadow: none; + } + + .lg\:hover\:shadow-xs:hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .lg\:hover\:shadow-sm:hover { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .lg\:hover\:shadow:hover { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .lg\:hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .lg\:hover\:shadow-xl:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .lg\:hover\:shadow-2xl:hover { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .lg\:hover\:shadow-inner:hover { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:hover\:shadow-outline:hover { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .lg\:hover\:shadow-none:hover { + box-shadow: none; + } + + .lg\:focus\:shadow-xs:focus { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .lg\:focus\:shadow-sm:focus { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .lg\:focus\:shadow:focus { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:focus\:shadow-md:focus { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .lg\:focus\:shadow-lg:focus { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .lg\:focus\:shadow-xl:focus { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .lg\:focus\:shadow-2xl:focus { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .lg\:focus\:shadow-inner:focus { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .lg\:focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .lg\:focus\:shadow-none:focus { + box-shadow: none; + } + + .lg\:fill-current { + fill: currentColor; + } + + .lg\:stroke-current { + stroke: currentColor; + } + + .lg\:stroke-0 { + stroke-width: 0; + } + + .lg\:stroke-1 { + stroke-width: 1; + } + + .lg\:stroke-2 { + stroke-width: 2; + } + + .lg\:table-auto { + table-layout: auto; + } + + .lg\:table-fixed { + table-layout: fixed; + } + + .lg\:text-left { + text-align: left; + } + + .lg\:text-center { + text-align: center; + } + + .lg\:text-right { + text-align: right; + } + + .lg\:text-justify { + text-align: justify; + } + + .lg\:text-transparent { + color: transparent; + } + + .lg\:text-current { + color: currentColor; + } + + .lg\:text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .lg\:text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .lg\:text-gray-100 { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .lg\:text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .lg\:text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .lg\:text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .lg\:text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .lg\:text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .lg\:text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .lg\:text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .lg\:text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .lg\:text-red-100 { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .lg\:text-red-200 { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .lg\:text-red-300 { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .lg\:text-red-400 { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .lg\:text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .lg\:text-red-600 { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .lg\:text-red-700 { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .lg\:text-red-800 { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .lg\:text-red-900 { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .lg\:text-orange-100 { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .lg\:text-orange-200 { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .lg\:text-orange-300 { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .lg\:text-orange-400 { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .lg\:text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .lg\:text-orange-600 { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .lg\:text-orange-700 { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .lg\:text-orange-800 { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .lg\:text-orange-900 { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .lg\:text-yellow-100 { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .lg\:text-yellow-200 { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .lg\:text-yellow-300 { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .lg\:text-yellow-400 { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .lg\:text-yellow-500 { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .lg\:text-yellow-600 { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .lg\:text-yellow-700 { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .lg\:text-yellow-800 { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .lg\:text-yellow-900 { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .lg\:text-green-100 { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .lg\:text-green-200 { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .lg\:text-green-300 { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .lg\:text-green-400 { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .lg\:text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .lg\:text-green-600 { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .lg\:text-green-700 { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .lg\:text-green-800 { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .lg\:text-green-900 { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .lg\:text-teal-100 { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .lg\:text-teal-200 { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .lg\:text-teal-300 { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .lg\:text-teal-400 { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .lg\:text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .lg\:text-teal-600 { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .lg\:text-teal-700 { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .lg\:text-teal-800 { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .lg\:text-teal-900 { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .lg\:text-blue-100 { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .lg\:text-blue-200 { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .lg\:text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .lg\:text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .lg\:text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .lg\:text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .lg\:text-blue-700 { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .lg\:text-blue-800 { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .lg\:text-blue-900 { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .lg\:text-indigo-100 { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .lg\:text-indigo-200 { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .lg\:text-indigo-300 { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .lg\:text-indigo-400 { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .lg\:text-indigo-500 { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .lg\:text-indigo-600 { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .lg\:text-indigo-700 { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .lg\:text-indigo-800 { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .lg\:text-indigo-900 { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .lg\:text-purple-100 { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .lg\:text-purple-200 { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .lg\:text-purple-300 { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .lg\:text-purple-400 { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .lg\:text-purple-500 { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .lg\:text-purple-600 { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .lg\:text-purple-700 { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .lg\:text-purple-800 { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .lg\:text-purple-900 { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .lg\:text-pink-100 { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .lg\:text-pink-200 { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .lg\:text-pink-300 { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .lg\:text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .lg\:text-pink-500 { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .lg\:text-pink-600 { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .lg\:text-pink-700 { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .lg\:text-pink-800 { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .lg\:text-pink-900 { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .lg\:hover\:text-transparent:hover { + color: transparent; + } + + .lg\:hover\:text-current:hover { + color: currentColor; + } + + .lg\:hover\:text-black:hover { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .lg\:hover\:text-white:hover { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .lg\:hover\:text-gray-100:hover { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .lg\:hover\:text-gray-200:hover { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .lg\:hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .lg\:hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .lg\:hover\:text-gray-500:hover { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .lg\:hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .lg\:hover\:text-gray-700:hover { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .lg\:hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .lg\:hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .lg\:hover\:text-red-100:hover { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .lg\:hover\:text-red-200:hover { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .lg\:hover\:text-red-300:hover { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .lg\:hover\:text-red-400:hover { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .lg\:hover\:text-red-500:hover { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .lg\:hover\:text-red-600:hover { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .lg\:hover\:text-red-700:hover { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .lg\:hover\:text-red-800:hover { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .lg\:hover\:text-red-900:hover { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .lg\:hover\:text-orange-100:hover { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .lg\:hover\:text-orange-200:hover { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .lg\:hover\:text-orange-300:hover { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .lg\:hover\:text-orange-400:hover { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .lg\:hover\:text-orange-500:hover { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .lg\:hover\:text-orange-600:hover { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .lg\:hover\:text-orange-700:hover { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .lg\:hover\:text-orange-800:hover { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .lg\:hover\:text-orange-900:hover { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-100:hover { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-200:hover { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-300:hover { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-400:hover { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-500:hover { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-600:hover { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-700:hover { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-800:hover { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .lg\:hover\:text-yellow-900:hover { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .lg\:hover\:text-green-100:hover { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .lg\:hover\:text-green-200:hover { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .lg\:hover\:text-green-300:hover { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .lg\:hover\:text-green-400:hover { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .lg\:hover\:text-green-500:hover { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .lg\:hover\:text-green-600:hover { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .lg\:hover\:text-green-700:hover { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .lg\:hover\:text-green-800:hover { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .lg\:hover\:text-green-900:hover { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .lg\:hover\:text-teal-100:hover { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .lg\:hover\:text-teal-200:hover { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .lg\:hover\:text-teal-300:hover { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .lg\:hover\:text-teal-400:hover { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .lg\:hover\:text-teal-500:hover { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .lg\:hover\:text-teal-600:hover { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .lg\:hover\:text-teal-700:hover { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .lg\:hover\:text-teal-800:hover { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .lg\:hover\:text-teal-900:hover { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .lg\:hover\:text-blue-100:hover { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .lg\:hover\:text-blue-200:hover { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .lg\:hover\:text-blue-300:hover { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .lg\:hover\:text-blue-400:hover { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .lg\:hover\:text-blue-500:hover { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .lg\:hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .lg\:hover\:text-blue-700:hover { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .lg\:hover\:text-blue-800:hover { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .lg\:hover\:text-blue-900:hover { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-100:hover { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-200:hover { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-300:hover { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-400:hover { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-500:hover { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-600:hover { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-700:hover { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-800:hover { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .lg\:hover\:text-indigo-900:hover { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .lg\:hover\:text-purple-100:hover { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .lg\:hover\:text-purple-200:hover { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .lg\:hover\:text-purple-300:hover { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .lg\:hover\:text-purple-400:hover { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .lg\:hover\:text-purple-500:hover { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .lg\:hover\:text-purple-600:hover { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .lg\:hover\:text-purple-700:hover { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .lg\:hover\:text-purple-800:hover { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .lg\:hover\:text-purple-900:hover { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .lg\:hover\:text-pink-100:hover { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .lg\:hover\:text-pink-200:hover { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .lg\:hover\:text-pink-300:hover { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .lg\:hover\:text-pink-400:hover { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .lg\:hover\:text-pink-500:hover { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .lg\:hover\:text-pink-600:hover { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .lg\:hover\:text-pink-700:hover { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .lg\:hover\:text-pink-800:hover { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .lg\:hover\:text-pink-900:hover { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .lg\:focus\:text-transparent:focus { + color: transparent; + } + + .lg\:focus\:text-current:focus { + color: currentColor; + } + + .lg\:focus\:text-black:focus { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .lg\:focus\:text-white:focus { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .lg\:focus\:text-gray-100:focus { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .lg\:focus\:text-gray-200:focus { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .lg\:focus\:text-gray-300:focus { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .lg\:focus\:text-gray-400:focus { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .lg\:focus\:text-gray-500:focus { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .lg\:focus\:text-gray-600:focus { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .lg\:focus\:text-gray-700:focus { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .lg\:focus\:text-gray-800:focus { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .lg\:focus\:text-gray-900:focus { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .lg\:focus\:text-red-100:focus { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .lg\:focus\:text-red-200:focus { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .lg\:focus\:text-red-300:focus { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .lg\:focus\:text-red-400:focus { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .lg\:focus\:text-red-500:focus { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .lg\:focus\:text-red-600:focus { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .lg\:focus\:text-red-700:focus { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .lg\:focus\:text-red-800:focus { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .lg\:focus\:text-red-900:focus { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .lg\:focus\:text-orange-100:focus { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .lg\:focus\:text-orange-200:focus { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .lg\:focus\:text-orange-300:focus { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .lg\:focus\:text-orange-400:focus { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .lg\:focus\:text-orange-500:focus { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .lg\:focus\:text-orange-600:focus { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .lg\:focus\:text-orange-700:focus { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .lg\:focus\:text-orange-800:focus { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .lg\:focus\:text-orange-900:focus { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-100:focus { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-200:focus { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-300:focus { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-400:focus { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-500:focus { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-600:focus { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-700:focus { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-800:focus { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .lg\:focus\:text-yellow-900:focus { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .lg\:focus\:text-green-100:focus { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .lg\:focus\:text-green-200:focus { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .lg\:focus\:text-green-300:focus { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .lg\:focus\:text-green-400:focus { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .lg\:focus\:text-green-500:focus { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .lg\:focus\:text-green-600:focus { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .lg\:focus\:text-green-700:focus { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .lg\:focus\:text-green-800:focus { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .lg\:focus\:text-green-900:focus { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .lg\:focus\:text-teal-100:focus { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .lg\:focus\:text-teal-200:focus { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .lg\:focus\:text-teal-300:focus { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .lg\:focus\:text-teal-400:focus { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .lg\:focus\:text-teal-500:focus { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .lg\:focus\:text-teal-600:focus { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .lg\:focus\:text-teal-700:focus { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .lg\:focus\:text-teal-800:focus { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .lg\:focus\:text-teal-900:focus { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .lg\:focus\:text-blue-100:focus { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .lg\:focus\:text-blue-200:focus { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .lg\:focus\:text-blue-300:focus { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .lg\:focus\:text-blue-400:focus { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .lg\:focus\:text-blue-500:focus { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .lg\:focus\:text-blue-600:focus { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .lg\:focus\:text-blue-700:focus { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .lg\:focus\:text-blue-800:focus { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .lg\:focus\:text-blue-900:focus { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-100:focus { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-200:focus { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-300:focus { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-400:focus { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-500:focus { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-600:focus { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-700:focus { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-800:focus { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .lg\:focus\:text-indigo-900:focus { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .lg\:focus\:text-purple-100:focus { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .lg\:focus\:text-purple-200:focus { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .lg\:focus\:text-purple-300:focus { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .lg\:focus\:text-purple-400:focus { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .lg\:focus\:text-purple-500:focus { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .lg\:focus\:text-purple-600:focus { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .lg\:focus\:text-purple-700:focus { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .lg\:focus\:text-purple-800:focus { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .lg\:focus\:text-purple-900:focus { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .lg\:focus\:text-pink-100:focus { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .lg\:focus\:text-pink-200:focus { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .lg\:focus\:text-pink-300:focus { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .lg\:focus\:text-pink-400:focus { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .lg\:focus\:text-pink-500:focus { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .lg\:focus\:text-pink-600:focus { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .lg\:focus\:text-pink-700:focus { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .lg\:focus\:text-pink-800:focus { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .lg\:focus\:text-pink-900:focus { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .lg\:text-opacity-0 { + --text-opacity: 0; + } + + .lg\:text-opacity-25 { + --text-opacity: 0.25; + } + + .lg\:text-opacity-50 { + --text-opacity: 0.5; + } + + .lg\:text-opacity-75 { + --text-opacity: 0.75; + } + + .lg\:text-opacity-100 { + --text-opacity: 1; + } + + .lg\:hover\:text-opacity-0:hover { + --text-opacity: 0; + } + + .lg\:hover\:text-opacity-25:hover { + --text-opacity: 0.25; + } + + .lg\:hover\:text-opacity-50:hover { + --text-opacity: 0.5; + } + + .lg\:hover\:text-opacity-75:hover { + --text-opacity: 0.75; + } + + .lg\:hover\:text-opacity-100:hover { + --text-opacity: 1; + } + + .lg\:focus\:text-opacity-0:focus { + --text-opacity: 0; + } + + .lg\:focus\:text-opacity-25:focus { + --text-opacity: 0.25; + } + + .lg\:focus\:text-opacity-50:focus { + --text-opacity: 0.5; + } + + .lg\:focus\:text-opacity-75:focus { + --text-opacity: 0.75; + } + + .lg\:focus\:text-opacity-100:focus { + --text-opacity: 1; + } + + .lg\:italic { + font-style: italic; + } + + .lg\:not-italic { + font-style: normal; + } + + .lg\:uppercase { + text-transform: uppercase; + } + + .lg\:lowercase { + text-transform: lowercase; + } + + .lg\:capitalize { + text-transform: capitalize; + } + + .lg\:normal-case { + text-transform: none; + } + + .lg\:underline { + text-decoration: underline; + } + + .lg\:line-through { + text-decoration: line-through; + } + + .lg\:no-underline { + text-decoration: none; + } + + .lg\:hover\:underline:hover { + text-decoration: underline; + } + + .lg\:hover\:line-through:hover { + text-decoration: line-through; + } + + .lg\:hover\:no-underline:hover { + text-decoration: none; + } + + .lg\:focus\:underline:focus { + text-decoration: underline; + } + + .lg\:focus\:line-through:focus { + text-decoration: line-through; + } + + .lg\:focus\:no-underline:focus { + text-decoration: none; + } + + .lg\:antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .lg\:subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + } + + .lg\:ordinal, .lg\:slashed-zero, .lg\:lining-nums, .lg\:oldstyle-nums, .lg\:proportional-nums, .lg\:tabular-nums, .lg\:diagonal-fractions, .lg\:stacked-fractions { + --font-variant-numeric-ordinal: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-slashed-zero: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-figure: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-spacing: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-fraction: var(--tailwind-empty,/*!*/ /*!*/); + font-variant-numeric: var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction); + } + + .lg\:normal-nums { + font-variant-numeric: normal; + } + + .lg\:ordinal { + --font-variant-numeric-ordinal: ordinal; + } + + .lg\:slashed-zero { + --font-variant-numeric-slashed-zero: slashed-zero; + } + + .lg\:lining-nums { + --font-variant-numeric-figure: lining-nums; + } + + .lg\:oldstyle-nums { + --font-variant-numeric-figure: oldstyle-nums; + } + + .lg\:proportional-nums { + --font-variant-numeric-spacing: proportional-nums; + } + + .lg\:tabular-nums { + --font-variant-numeric-spacing: tabular-nums; + } + + .lg\:diagonal-fractions { + --font-variant-numeric-fraction: diagonal-fractions; + } + + .lg\:stacked-fractions { + --font-variant-numeric-fraction: stacked-fractions; + } + + .lg\:tracking-tighter { + letter-spacing: -0.05em; + } + + .lg\:tracking-tight { + letter-spacing: -0.025em; + } + + .lg\:tracking-normal { + letter-spacing: 0; + } + + .lg\:tracking-wide { + letter-spacing: 0.025em; + } + + .lg\:tracking-wider { + letter-spacing: 0.05em; + } + + .lg\:tracking-widest { + letter-spacing: 0.1em; + } + + .lg\:select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .lg\:select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .lg\:select-all { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + } + + .lg\:select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; + } + + .lg\:align-baseline { + vertical-align: baseline; + } + + .lg\:align-top { + vertical-align: top; + } + + .lg\:align-middle { + vertical-align: middle; + } + + .lg\:align-bottom { + vertical-align: bottom; + } + + .lg\:align-text-top { + vertical-align: text-top; + } + + .lg\:align-text-bottom { + vertical-align: text-bottom; + } + + .lg\:visible { + visibility: visible; + } + + .lg\:invisible { + visibility: hidden; + } + + .lg\:whitespace-normal { + white-space: normal; + } + + .lg\:whitespace-no-wrap { + white-space: nowrap; + } + + .lg\:whitespace-pre { + white-space: pre; + } + + .lg\:whitespace-pre-line { + white-space: pre-line; + } + + .lg\:whitespace-pre-wrap { + white-space: pre-wrap; + } + + .lg\:break-normal { + overflow-wrap: normal; + word-break: normal; + } + + .lg\:break-words { + overflow-wrap: break-word; + } + + .lg\:break-all { + word-break: break-all; + } + + .lg\:truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .lg\:w-0 { + width: 0; + } + + .lg\:w-1 { + width: 0.25rem; + } + + .lg\:w-2 { + width: 0.5rem; + } + + .lg\:w-3 { + width: 0.75rem; + } + + .lg\:w-4 { + width: 1rem; + } + + .lg\:w-5 { + width: 1.25rem; + } + + .lg\:w-6 { + width: 1.5rem; + } + + .lg\:w-8 { + width: 2rem; + } + + .lg\:w-10 { + width: 2.5rem; + } + + .lg\:w-12 { + width: 3rem; + } + + .lg\:w-16 { + width: 4rem; + } + + .lg\:w-20 { + width: 5rem; + } + + .lg\:w-24 { + width: 6rem; + } + + .lg\:w-32 { + width: 8rem; + } + + .lg\:w-40 { + width: 10rem; + } + + .lg\:w-48 { + width: 12rem; + } + + .lg\:w-56 { + width: 14rem; + } + + .lg\:w-64 { + width: 16rem; + } + + .lg\:w-auto { + width: auto; + } + + .lg\:w-px { + width: 1px; + } + + .lg\:w-1\/2 { + width: 50%; + } + + .lg\:w-1\/3 { + width: 33.333333%; + } + + .lg\:w-2\/3 { + width: 66.666667%; + } + + .lg\:w-1\/4 { + width: 25%; + } + + .lg\:w-2\/4 { + width: 50%; + } + + .lg\:w-3\/4 { + width: 75%; + } + + .lg\:w-1\/5 { + width: 20%; + } + + .lg\:w-2\/5 { + width: 40%; + } + + .lg\:w-3\/5 { + width: 60%; + } + + .lg\:w-4\/5 { + width: 80%; + } + + .lg\:w-1\/6 { + width: 16.666667%; + } + + .lg\:w-2\/6 { + width: 33.333333%; + } + + .lg\:w-3\/6 { + width: 50%; + } + + .lg\:w-4\/6 { + width: 66.666667%; + } + + .lg\:w-5\/6 { + width: 83.333333%; + } + + .lg\:w-1\/12 { + width: 8.333333%; + } + + .lg\:w-2\/12 { + width: 16.666667%; + } + + .lg\:w-3\/12 { + width: 25%; + } + + .lg\:w-4\/12 { + width: 33.333333%; + } + + .lg\:w-5\/12 { + width: 41.666667%; + } + + .lg\:w-6\/12 { + width: 50%; + } + + .lg\:w-7\/12 { + width: 58.333333%; + } + + .lg\:w-8\/12 { + width: 66.666667%; + } + + .lg\:w-9\/12 { + width: 75%; + } + + .lg\:w-10\/12 { + width: 83.333333%; + } + + .lg\:w-11\/12 { + width: 91.666667%; + } + + .lg\:w-full { + width: 100%; + } + + .lg\:w-screen { + width: 100vw; + } + + .lg\:z-0 { + z-index: 0; + } + + .lg\:z-10 { + z-index: 10; + } + + .lg\:z-20 { + z-index: 20; + } + + .lg\:z-30 { + z-index: 30; + } + + .lg\:z-40 { + z-index: 40; + } + + .lg\:z-50 { + z-index: 50; + } + + .lg\:z-auto { + z-index: auto; + } + + .lg\:gap-0 { + grid-gap: 0; + gap: 0; + } + + .lg\:gap-1 { + grid-gap: 0.25rem; + gap: 0.25rem; + } + + .lg\:gap-2 { + grid-gap: 0.5rem; + gap: 0.5rem; + } + + .lg\:gap-3 { + grid-gap: 0.75rem; + gap: 0.75rem; + } + + .lg\:gap-4 { + grid-gap: 1rem; + gap: 1rem; + } + + .lg\:gap-5 { + grid-gap: 1.25rem; + gap: 1.25rem; + } + + .lg\:gap-6 { + grid-gap: 1.5rem; + gap: 1.5rem; + } + + .lg\:gap-8 { + grid-gap: 2rem; + gap: 2rem; + } + + .lg\:gap-10 { + grid-gap: 2.5rem; + gap: 2.5rem; + } + + .lg\:gap-12 { + grid-gap: 3rem; + gap: 3rem; + } + + .lg\:gap-16 { + grid-gap: 4rem; + gap: 4rem; + } + + .lg\:gap-20 { + grid-gap: 5rem; + gap: 5rem; + } + + .lg\:gap-24 { + grid-gap: 6rem; + gap: 6rem; + } + + .lg\:gap-32 { + grid-gap: 8rem; + gap: 8rem; + } + + .lg\:gap-40 { + grid-gap: 10rem; + gap: 10rem; + } + + .lg\:gap-48 { + grid-gap: 12rem; + gap: 12rem; + } + + .lg\:gap-56 { + grid-gap: 14rem; + gap: 14rem; + } + + .lg\:gap-64 { + grid-gap: 16rem; + gap: 16rem; + } + + .lg\:gap-px { + grid-gap: 1px; + gap: 1px; + } + + .lg\:col-gap-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .lg\:col-gap-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .lg\:col-gap-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .lg\:col-gap-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .lg\:col-gap-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .lg\:col-gap-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .lg\:col-gap-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .lg\:col-gap-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .lg\:col-gap-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .lg\:col-gap-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .lg\:col-gap-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .lg\:col-gap-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .lg\:col-gap-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .lg\:col-gap-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .lg\:col-gap-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .lg\:col-gap-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .lg\:col-gap-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .lg\:col-gap-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .lg\:col-gap-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .lg\:gap-x-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .lg\:gap-x-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .lg\:gap-x-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .lg\:gap-x-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .lg\:gap-x-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .lg\:gap-x-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .lg\:gap-x-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .lg\:gap-x-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .lg\:gap-x-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .lg\:gap-x-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .lg\:gap-x-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .lg\:gap-x-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .lg\:gap-x-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .lg\:gap-x-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .lg\:gap-x-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .lg\:gap-x-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .lg\:gap-x-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .lg\:gap-x-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .lg\:gap-x-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .lg\:row-gap-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .lg\:row-gap-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .lg\:row-gap-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .lg\:row-gap-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .lg\:row-gap-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .lg\:row-gap-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .lg\:row-gap-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .lg\:row-gap-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .lg\:row-gap-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .lg\:row-gap-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .lg\:row-gap-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .lg\:row-gap-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .lg\:row-gap-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .lg\:row-gap-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .lg\:row-gap-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .lg\:row-gap-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .lg\:row-gap-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .lg\:row-gap-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .lg\:row-gap-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .lg\:gap-y-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .lg\:gap-y-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .lg\:gap-y-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .lg\:gap-y-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .lg\:gap-y-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .lg\:gap-y-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .lg\:gap-y-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .lg\:gap-y-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .lg\:gap-y-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .lg\:gap-y-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .lg\:gap-y-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .lg\:gap-y-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .lg\:gap-y-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .lg\:gap-y-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .lg\:gap-y-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .lg\:gap-y-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .lg\:gap-y-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .lg\:gap-y-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .lg\:gap-y-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .lg\:grid-flow-row { + grid-auto-flow: row; + } + + .lg\:grid-flow-col { + grid-auto-flow: column; + } + + .lg\:grid-flow-row-dense { + grid-auto-flow: row dense; + } + + .lg\:grid-flow-col-dense { + grid-auto-flow: column dense; + } + + .lg\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .lg\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .lg\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .lg\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .lg\:grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + .lg\:grid-cols-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + + .lg\:grid-cols-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + + .lg\:grid-cols-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + + .lg\:grid-cols-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + + .lg\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + + .lg\:grid-cols-none { + grid-template-columns: none; + } + + .lg\:col-auto { + grid-column: auto; + } + + .lg\:col-span-1 { + grid-column: span 1 / span 1; + } + + .lg\:col-span-2 { + grid-column: span 2 / span 2; + } + + .lg\:col-span-3 { + grid-column: span 3 / span 3; + } + + .lg\:col-span-4 { + grid-column: span 4 / span 4; + } + + .lg\:col-span-5 { + grid-column: span 5 / span 5; + } + + .lg\:col-span-6 { + grid-column: span 6 / span 6; + } + + .lg\:col-span-7 { + grid-column: span 7 / span 7; + } + + .lg\:col-span-8 { + grid-column: span 8 / span 8; + } + + .lg\:col-span-9 { + grid-column: span 9 / span 9; + } + + .lg\:col-span-10 { + grid-column: span 10 / span 10; + } + + .lg\:col-span-11 { + grid-column: span 11 / span 11; + } + + .lg\:col-span-12 { + grid-column: span 12 / span 12; + } + + .lg\:col-start-1 { + grid-column-start: 1; + } + + .lg\:col-start-2 { + grid-column-start: 2; + } + + .lg\:col-start-3 { + grid-column-start: 3; + } + + .lg\:col-start-4 { + grid-column-start: 4; + } + + .lg\:col-start-5 { + grid-column-start: 5; + } + + .lg\:col-start-6 { + grid-column-start: 6; + } + + .lg\:col-start-7 { + grid-column-start: 7; + } + + .lg\:col-start-8 { + grid-column-start: 8; + } + + .lg\:col-start-9 { + grid-column-start: 9; + } + + .lg\:col-start-10 { + grid-column-start: 10; + } + + .lg\:col-start-11 { + grid-column-start: 11; + } + + .lg\:col-start-12 { + grid-column-start: 12; + } + + .lg\:col-start-13 { + grid-column-start: 13; + } + + .lg\:col-start-auto { + grid-column-start: auto; + } + + .lg\:col-end-1 { + grid-column-end: 1; + } + + .lg\:col-end-2 { + grid-column-end: 2; + } + + .lg\:col-end-3 { + grid-column-end: 3; + } + + .lg\:col-end-4 { + grid-column-end: 4; + } + + .lg\:col-end-5 { + grid-column-end: 5; + } + + .lg\:col-end-6 { + grid-column-end: 6; + } + + .lg\:col-end-7 { + grid-column-end: 7; + } + + .lg\:col-end-8 { + grid-column-end: 8; + } + + .lg\:col-end-9 { + grid-column-end: 9; + } + + .lg\:col-end-10 { + grid-column-end: 10; + } + + .lg\:col-end-11 { + grid-column-end: 11; + } + + .lg\:col-end-12 { + grid-column-end: 12; + } + + .lg\:col-end-13 { + grid-column-end: 13; + } + + .lg\:col-end-auto { + grid-column-end: auto; + } + + .lg\:grid-rows-1 { + grid-template-rows: repeat(1, minmax(0, 1fr)); + } + + .lg\:grid-rows-2 { + grid-template-rows: repeat(2, minmax(0, 1fr)); + } + + .lg\:grid-rows-3 { + grid-template-rows: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-rows-4 { + grid-template-rows: repeat(4, minmax(0, 1fr)); + } + + .lg\:grid-rows-5 { + grid-template-rows: repeat(5, minmax(0, 1fr)); + } + + .lg\:grid-rows-6 { + grid-template-rows: repeat(6, minmax(0, 1fr)); + } + + .lg\:grid-rows-none { + grid-template-rows: none; + } + + .lg\:row-auto { + grid-row: auto; + } + + .lg\:row-span-1 { + grid-row: span 1 / span 1; + } + + .lg\:row-span-2 { + grid-row: span 2 / span 2; + } + + .lg\:row-span-3 { + grid-row: span 3 / span 3; + } + + .lg\:row-span-4 { + grid-row: span 4 / span 4; + } + + .lg\:row-span-5 { + grid-row: span 5 / span 5; + } + + .lg\:row-span-6 { + grid-row: span 6 / span 6; + } + + .lg\:row-start-1 { + grid-row-start: 1; + } + + .lg\:row-start-2 { + grid-row-start: 2; + } + + .lg\:row-start-3 { + grid-row-start: 3; + } + + .lg\:row-start-4 { + grid-row-start: 4; + } + + .lg\:row-start-5 { + grid-row-start: 5; + } + + .lg\:row-start-6 { + grid-row-start: 6; + } + + .lg\:row-start-7 { + grid-row-start: 7; + } + + .lg\:row-start-auto { + grid-row-start: auto; + } + + .lg\:row-end-1 { + grid-row-end: 1; + } + + .lg\:row-end-2 { + grid-row-end: 2; + } + + .lg\:row-end-3 { + grid-row-end: 3; + } + + .lg\:row-end-4 { + grid-row-end: 4; + } + + .lg\:row-end-5 { + grid-row-end: 5; + } + + .lg\:row-end-6 { + grid-row-end: 6; + } + + .lg\:row-end-7 { + grid-row-end: 7; + } + + .lg\:row-end-auto { + grid-row-end: auto; + } + + .lg\:transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); + } + + .lg\:transform-none { + transform: none; + } + + .lg\:origin-center { + transform-origin: center; + } + + .lg\:origin-top { + transform-origin: top; + } + + .lg\:origin-top-right { + transform-origin: top right; + } + + .lg\:origin-right { + transform-origin: right; + } + + .lg\:origin-bottom-right { + transform-origin: bottom right; + } + + .lg\:origin-bottom { + transform-origin: bottom; + } + + .lg\:origin-bottom-left { + transform-origin: bottom left; + } + + .lg\:origin-left { + transform-origin: left; + } + + .lg\:origin-top-left { + transform-origin: top left; + } + + .lg\:scale-0 { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .lg\:scale-50 { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .lg\:scale-75 { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .lg\:scale-90 { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .lg\:scale-95 { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .lg\:scale-100 { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .lg\:scale-105 { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .lg\:scale-110 { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .lg\:scale-125 { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .lg\:scale-150 { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .lg\:scale-x-0 { + --transform-scale-x: 0; + } + + .lg\:scale-x-50 { + --transform-scale-x: .5; + } + + .lg\:scale-x-75 { + --transform-scale-x: .75; + } + + .lg\:scale-x-90 { + --transform-scale-x: .9; + } + + .lg\:scale-x-95 { + --transform-scale-x: .95; + } + + .lg\:scale-x-100 { + --transform-scale-x: 1; + } + + .lg\:scale-x-105 { + --transform-scale-x: 1.05; + } + + .lg\:scale-x-110 { + --transform-scale-x: 1.1; + } + + .lg\:scale-x-125 { + --transform-scale-x: 1.25; + } + + .lg\:scale-x-150 { + --transform-scale-x: 1.5; + } + + .lg\:scale-y-0 { + --transform-scale-y: 0; + } + + .lg\:scale-y-50 { + --transform-scale-y: .5; + } + + .lg\:scale-y-75 { + --transform-scale-y: .75; + } + + .lg\:scale-y-90 { + --transform-scale-y: .9; + } + + .lg\:scale-y-95 { + --transform-scale-y: .95; + } + + .lg\:scale-y-100 { + --transform-scale-y: 1; + } + + .lg\:scale-y-105 { + --transform-scale-y: 1.05; + } + + .lg\:scale-y-110 { + --transform-scale-y: 1.1; + } + + .lg\:scale-y-125 { + --transform-scale-y: 1.25; + } + + .lg\:scale-y-150 { + --transform-scale-y: 1.5; + } + + .lg\:hover\:scale-0:hover { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .lg\:hover\:scale-50:hover { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .lg\:hover\:scale-75:hover { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .lg\:hover\:scale-90:hover { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .lg\:hover\:scale-95:hover { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .lg\:hover\:scale-100:hover { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .lg\:hover\:scale-105:hover { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .lg\:hover\:scale-110:hover { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .lg\:hover\:scale-125:hover { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .lg\:hover\:scale-150:hover { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .lg\:hover\:scale-x-0:hover { + --transform-scale-x: 0; + } + + .lg\:hover\:scale-x-50:hover { + --transform-scale-x: .5; + } + + .lg\:hover\:scale-x-75:hover { + --transform-scale-x: .75; + } + + .lg\:hover\:scale-x-90:hover { + --transform-scale-x: .9; + } + + .lg\:hover\:scale-x-95:hover { + --transform-scale-x: .95; + } + + .lg\:hover\:scale-x-100:hover { + --transform-scale-x: 1; + } + + .lg\:hover\:scale-x-105:hover { + --transform-scale-x: 1.05; + } + + .lg\:hover\:scale-x-110:hover { + --transform-scale-x: 1.1; + } + + .lg\:hover\:scale-x-125:hover { + --transform-scale-x: 1.25; + } + + .lg\:hover\:scale-x-150:hover { + --transform-scale-x: 1.5; + } + + .lg\:hover\:scale-y-0:hover { + --transform-scale-y: 0; + } + + .lg\:hover\:scale-y-50:hover { + --transform-scale-y: .5; + } + + .lg\:hover\:scale-y-75:hover { + --transform-scale-y: .75; + } + + .lg\:hover\:scale-y-90:hover { + --transform-scale-y: .9; + } + + .lg\:hover\:scale-y-95:hover { + --transform-scale-y: .95; + } + + .lg\:hover\:scale-y-100:hover { + --transform-scale-y: 1; + } + + .lg\:hover\:scale-y-105:hover { + --transform-scale-y: 1.05; + } + + .lg\:hover\:scale-y-110:hover { + --transform-scale-y: 1.1; + } + + .lg\:hover\:scale-y-125:hover { + --transform-scale-y: 1.25; + } + + .lg\:hover\:scale-y-150:hover { + --transform-scale-y: 1.5; + } + + .lg\:focus\:scale-0:focus { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .lg\:focus\:scale-50:focus { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .lg\:focus\:scale-75:focus { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .lg\:focus\:scale-90:focus { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .lg\:focus\:scale-95:focus { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .lg\:focus\:scale-100:focus { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .lg\:focus\:scale-105:focus { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .lg\:focus\:scale-110:focus { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .lg\:focus\:scale-125:focus { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .lg\:focus\:scale-150:focus { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .lg\:focus\:scale-x-0:focus { + --transform-scale-x: 0; + } + + .lg\:focus\:scale-x-50:focus { + --transform-scale-x: .5; + } + + .lg\:focus\:scale-x-75:focus { + --transform-scale-x: .75; + } + + .lg\:focus\:scale-x-90:focus { + --transform-scale-x: .9; + } + + .lg\:focus\:scale-x-95:focus { + --transform-scale-x: .95; + } + + .lg\:focus\:scale-x-100:focus { + --transform-scale-x: 1; + } + + .lg\:focus\:scale-x-105:focus { + --transform-scale-x: 1.05; + } + + .lg\:focus\:scale-x-110:focus { + --transform-scale-x: 1.1; + } + + .lg\:focus\:scale-x-125:focus { + --transform-scale-x: 1.25; + } + + .lg\:focus\:scale-x-150:focus { + --transform-scale-x: 1.5; + } + + .lg\:focus\:scale-y-0:focus { + --transform-scale-y: 0; + } + + .lg\:focus\:scale-y-50:focus { + --transform-scale-y: .5; + } + + .lg\:focus\:scale-y-75:focus { + --transform-scale-y: .75; + } + + .lg\:focus\:scale-y-90:focus { + --transform-scale-y: .9; + } + + .lg\:focus\:scale-y-95:focus { + --transform-scale-y: .95; + } + + .lg\:focus\:scale-y-100:focus { + --transform-scale-y: 1; + } + + .lg\:focus\:scale-y-105:focus { + --transform-scale-y: 1.05; + } + + .lg\:focus\:scale-y-110:focus { + --transform-scale-y: 1.1; + } + + .lg\:focus\:scale-y-125:focus { + --transform-scale-y: 1.25; + } + + .lg\:focus\:scale-y-150:focus { + --transform-scale-y: 1.5; + } + + .lg\:rotate-0 { + --transform-rotate: 0; + } + + .lg\:rotate-45 { + --transform-rotate: 45deg; + } + + .lg\:rotate-90 { + --transform-rotate: 90deg; + } + + .lg\:rotate-180 { + --transform-rotate: 180deg; + } + + .lg\:-rotate-180 { + --transform-rotate: -180deg; + } + + .lg\:-rotate-90 { + --transform-rotate: -90deg; + } + + .lg\:-rotate-45 { + --transform-rotate: -45deg; + } + + .lg\:hover\:rotate-0:hover { + --transform-rotate: 0; + } + + .lg\:hover\:rotate-45:hover { + --transform-rotate: 45deg; + } + + .lg\:hover\:rotate-90:hover { + --transform-rotate: 90deg; + } + + .lg\:hover\:rotate-180:hover { + --transform-rotate: 180deg; + } + + .lg\:hover\:-rotate-180:hover { + --transform-rotate: -180deg; + } + + .lg\:hover\:-rotate-90:hover { + --transform-rotate: -90deg; + } + + .lg\:hover\:-rotate-45:hover { + --transform-rotate: -45deg; + } + + .lg\:focus\:rotate-0:focus { + --transform-rotate: 0; + } + + .lg\:focus\:rotate-45:focus { + --transform-rotate: 45deg; + } + + .lg\:focus\:rotate-90:focus { + --transform-rotate: 90deg; + } + + .lg\:focus\:rotate-180:focus { + --transform-rotate: 180deg; + } + + .lg\:focus\:-rotate-180:focus { + --transform-rotate: -180deg; + } + + .lg\:focus\:-rotate-90:focus { + --transform-rotate: -90deg; + } + + .lg\:focus\:-rotate-45:focus { + --transform-rotate: -45deg; + } + + .lg\:translate-x-0 { + --transform-translate-x: 0; + } + + .lg\:translate-x-1 { + --transform-translate-x: 0.25rem; + } + + .lg\:translate-x-2 { + --transform-translate-x: 0.5rem; + } + + .lg\:translate-x-3 { + --transform-translate-x: 0.75rem; + } + + .lg\:translate-x-4 { + --transform-translate-x: 1rem; + } + + .lg\:translate-x-5 { + --transform-translate-x: 1.25rem; + } + + .lg\:translate-x-6 { + --transform-translate-x: 1.5rem; + } + + .lg\:translate-x-8 { + --transform-translate-x: 2rem; + } + + .lg\:translate-x-10 { + --transform-translate-x: 2.5rem; + } + + .lg\:translate-x-12 { + --transform-translate-x: 3rem; + } + + .lg\:translate-x-16 { + --transform-translate-x: 4rem; + } + + .lg\:translate-x-20 { + --transform-translate-x: 5rem; + } + + .lg\:translate-x-24 { + --transform-translate-x: 6rem; + } + + .lg\:translate-x-32 { + --transform-translate-x: 8rem; + } + + .lg\:translate-x-40 { + --transform-translate-x: 10rem; + } + + .lg\:translate-x-48 { + --transform-translate-x: 12rem; + } + + .lg\:translate-x-56 { + --transform-translate-x: 14rem; + } + + .lg\:translate-x-64 { + --transform-translate-x: 16rem; + } + + .lg\:translate-x-px { + --transform-translate-x: 1px; + } + + .lg\:-translate-x-1 { + --transform-translate-x: -0.25rem; + } + + .lg\:-translate-x-2 { + --transform-translate-x: -0.5rem; + } + + .lg\:-translate-x-3 { + --transform-translate-x: -0.75rem; + } + + .lg\:-translate-x-4 { + --transform-translate-x: -1rem; + } + + .lg\:-translate-x-5 { + --transform-translate-x: -1.25rem; + } + + .lg\:-translate-x-6 { + --transform-translate-x: -1.5rem; + } + + .lg\:-translate-x-8 { + --transform-translate-x: -2rem; + } + + .lg\:-translate-x-10 { + --transform-translate-x: -2.5rem; + } + + .lg\:-translate-x-12 { + --transform-translate-x: -3rem; + } + + .lg\:-translate-x-16 { + --transform-translate-x: -4rem; + } + + .lg\:-translate-x-20 { + --transform-translate-x: -5rem; + } + + .lg\:-translate-x-24 { + --transform-translate-x: -6rem; + } + + .lg\:-translate-x-32 { + --transform-translate-x: -8rem; + } + + .lg\:-translate-x-40 { + --transform-translate-x: -10rem; + } + + .lg\:-translate-x-48 { + --transform-translate-x: -12rem; + } + + .lg\:-translate-x-56 { + --transform-translate-x: -14rem; + } + + .lg\:-translate-x-64 { + --transform-translate-x: -16rem; + } + + .lg\:-translate-x-px { + --transform-translate-x: -1px; + } + + .lg\:-translate-x-full { + --transform-translate-x: -100%; + } + + .lg\:-translate-x-1\/2 { + --transform-translate-x: -50%; + } + + .lg\:translate-x-1\/2 { + --transform-translate-x: 50%; + } + + .lg\:translate-x-full { + --transform-translate-x: 100%; + } + + .lg\:translate-y-0 { + --transform-translate-y: 0; + } + + .lg\:translate-y-1 { + --transform-translate-y: 0.25rem; + } + + .lg\:translate-y-2 { + --transform-translate-y: 0.5rem; + } + + .lg\:translate-y-3 { + --transform-translate-y: 0.75rem; + } + + .lg\:translate-y-4 { + --transform-translate-y: 1rem; + } + + .lg\:translate-y-5 { + --transform-translate-y: 1.25rem; + } + + .lg\:translate-y-6 { + --transform-translate-y: 1.5rem; + } + + .lg\:translate-y-8 { + --transform-translate-y: 2rem; + } + + .lg\:translate-y-10 { + --transform-translate-y: 2.5rem; + } + + .lg\:translate-y-12 { + --transform-translate-y: 3rem; + } + + .lg\:translate-y-16 { + --transform-translate-y: 4rem; + } + + .lg\:translate-y-20 { + --transform-translate-y: 5rem; + } + + .lg\:translate-y-24 { + --transform-translate-y: 6rem; + } + + .lg\:translate-y-32 { + --transform-translate-y: 8rem; + } + + .lg\:translate-y-40 { + --transform-translate-y: 10rem; + } + + .lg\:translate-y-48 { + --transform-translate-y: 12rem; + } + + .lg\:translate-y-56 { + --transform-translate-y: 14rem; + } + + .lg\:translate-y-64 { + --transform-translate-y: 16rem; + } + + .lg\:translate-y-px { + --transform-translate-y: 1px; + } + + .lg\:-translate-y-1 { + --transform-translate-y: -0.25rem; + } + + .lg\:-translate-y-2 { + --transform-translate-y: -0.5rem; + } + + .lg\:-translate-y-3 { + --transform-translate-y: -0.75rem; + } + + .lg\:-translate-y-4 { + --transform-translate-y: -1rem; + } + + .lg\:-translate-y-5 { + --transform-translate-y: -1.25rem; + } + + .lg\:-translate-y-6 { + --transform-translate-y: -1.5rem; + } + + .lg\:-translate-y-8 { + --transform-translate-y: -2rem; + } + + .lg\:-translate-y-10 { + --transform-translate-y: -2.5rem; + } + + .lg\:-translate-y-12 { + --transform-translate-y: -3rem; + } + + .lg\:-translate-y-16 { + --transform-translate-y: -4rem; + } + + .lg\:-translate-y-20 { + --transform-translate-y: -5rem; + } + + .lg\:-translate-y-24 { + --transform-translate-y: -6rem; + } + + .lg\:-translate-y-32 { + --transform-translate-y: -8rem; + } + + .lg\:-translate-y-40 { + --transform-translate-y: -10rem; + } + + .lg\:-translate-y-48 { + --transform-translate-y: -12rem; + } + + .lg\:-translate-y-56 { + --transform-translate-y: -14rem; + } + + .lg\:-translate-y-64 { + --transform-translate-y: -16rem; + } + + .lg\:-translate-y-px { + --transform-translate-y: -1px; + } + + .lg\:-translate-y-full { + --transform-translate-y: -100%; + } + + .lg\:-translate-y-1\/2 { + --transform-translate-y: -50%; + } + + .lg\:translate-y-1\/2 { + --transform-translate-y: 50%; + } + + .lg\:translate-y-full { + --transform-translate-y: 100%; + } + + .lg\:hover\:translate-x-0:hover { + --transform-translate-x: 0; + } + + .lg\:hover\:translate-x-1:hover { + --transform-translate-x: 0.25rem; + } + + .lg\:hover\:translate-x-2:hover { + --transform-translate-x: 0.5rem; + } + + .lg\:hover\:translate-x-3:hover { + --transform-translate-x: 0.75rem; + } + + .lg\:hover\:translate-x-4:hover { + --transform-translate-x: 1rem; + } + + .lg\:hover\:translate-x-5:hover { + --transform-translate-x: 1.25rem; + } + + .lg\:hover\:translate-x-6:hover { + --transform-translate-x: 1.5rem; + } + + .lg\:hover\:translate-x-8:hover { + --transform-translate-x: 2rem; + } + + .lg\:hover\:translate-x-10:hover { + --transform-translate-x: 2.5rem; + } + + .lg\:hover\:translate-x-12:hover { + --transform-translate-x: 3rem; + } + + .lg\:hover\:translate-x-16:hover { + --transform-translate-x: 4rem; + } + + .lg\:hover\:translate-x-20:hover { + --transform-translate-x: 5rem; + } + + .lg\:hover\:translate-x-24:hover { + --transform-translate-x: 6rem; + } + + .lg\:hover\:translate-x-32:hover { + --transform-translate-x: 8rem; + } + + .lg\:hover\:translate-x-40:hover { + --transform-translate-x: 10rem; + } + + .lg\:hover\:translate-x-48:hover { + --transform-translate-x: 12rem; + } + + .lg\:hover\:translate-x-56:hover { + --transform-translate-x: 14rem; + } + + .lg\:hover\:translate-x-64:hover { + --transform-translate-x: 16rem; + } + + .lg\:hover\:translate-x-px:hover { + --transform-translate-x: 1px; + } + + .lg\:hover\:-translate-x-1:hover { + --transform-translate-x: -0.25rem; + } + + .lg\:hover\:-translate-x-2:hover { + --transform-translate-x: -0.5rem; + } + + .lg\:hover\:-translate-x-3:hover { + --transform-translate-x: -0.75rem; + } + + .lg\:hover\:-translate-x-4:hover { + --transform-translate-x: -1rem; + } + + .lg\:hover\:-translate-x-5:hover { + --transform-translate-x: -1.25rem; + } + + .lg\:hover\:-translate-x-6:hover { + --transform-translate-x: -1.5rem; + } + + .lg\:hover\:-translate-x-8:hover { + --transform-translate-x: -2rem; + } + + .lg\:hover\:-translate-x-10:hover { + --transform-translate-x: -2.5rem; + } + + .lg\:hover\:-translate-x-12:hover { + --transform-translate-x: -3rem; + } + + .lg\:hover\:-translate-x-16:hover { + --transform-translate-x: -4rem; + } + + .lg\:hover\:-translate-x-20:hover { + --transform-translate-x: -5rem; + } + + .lg\:hover\:-translate-x-24:hover { + --transform-translate-x: -6rem; + } + + .lg\:hover\:-translate-x-32:hover { + --transform-translate-x: -8rem; + } + + .lg\:hover\:-translate-x-40:hover { + --transform-translate-x: -10rem; + } + + .lg\:hover\:-translate-x-48:hover { + --transform-translate-x: -12rem; + } + + .lg\:hover\:-translate-x-56:hover { + --transform-translate-x: -14rem; + } + + .lg\:hover\:-translate-x-64:hover { + --transform-translate-x: -16rem; + } + + .lg\:hover\:-translate-x-px:hover { + --transform-translate-x: -1px; + } + + .lg\:hover\:-translate-x-full:hover { + --transform-translate-x: -100%; + } + + .lg\:hover\:-translate-x-1\/2:hover { + --transform-translate-x: -50%; + } + + .lg\:hover\:translate-x-1\/2:hover { + --transform-translate-x: 50%; + } + + .lg\:hover\:translate-x-full:hover { + --transform-translate-x: 100%; + } + + .lg\:hover\:translate-y-0:hover { + --transform-translate-y: 0; + } + + .lg\:hover\:translate-y-1:hover { + --transform-translate-y: 0.25rem; + } + + .lg\:hover\:translate-y-2:hover { + --transform-translate-y: 0.5rem; + } + + .lg\:hover\:translate-y-3:hover { + --transform-translate-y: 0.75rem; + } + + .lg\:hover\:translate-y-4:hover { + --transform-translate-y: 1rem; + } + + .lg\:hover\:translate-y-5:hover { + --transform-translate-y: 1.25rem; + } + + .lg\:hover\:translate-y-6:hover { + --transform-translate-y: 1.5rem; + } + + .lg\:hover\:translate-y-8:hover { + --transform-translate-y: 2rem; + } + + .lg\:hover\:translate-y-10:hover { + --transform-translate-y: 2.5rem; + } + + .lg\:hover\:translate-y-12:hover { + --transform-translate-y: 3rem; + } + + .lg\:hover\:translate-y-16:hover { + --transform-translate-y: 4rem; + } + + .lg\:hover\:translate-y-20:hover { + --transform-translate-y: 5rem; + } + + .lg\:hover\:translate-y-24:hover { + --transform-translate-y: 6rem; + } + + .lg\:hover\:translate-y-32:hover { + --transform-translate-y: 8rem; + } + + .lg\:hover\:translate-y-40:hover { + --transform-translate-y: 10rem; + } + + .lg\:hover\:translate-y-48:hover { + --transform-translate-y: 12rem; + } + + .lg\:hover\:translate-y-56:hover { + --transform-translate-y: 14rem; + } + + .lg\:hover\:translate-y-64:hover { + --transform-translate-y: 16rem; + } + + .lg\:hover\:translate-y-px:hover { + --transform-translate-y: 1px; + } + + .lg\:hover\:-translate-y-1:hover { + --transform-translate-y: -0.25rem; + } + + .lg\:hover\:-translate-y-2:hover { + --transform-translate-y: -0.5rem; + } + + .lg\:hover\:-translate-y-3:hover { + --transform-translate-y: -0.75rem; + } + + .lg\:hover\:-translate-y-4:hover { + --transform-translate-y: -1rem; + } + + .lg\:hover\:-translate-y-5:hover { + --transform-translate-y: -1.25rem; + } + + .lg\:hover\:-translate-y-6:hover { + --transform-translate-y: -1.5rem; + } + + .lg\:hover\:-translate-y-8:hover { + --transform-translate-y: -2rem; + } + + .lg\:hover\:-translate-y-10:hover { + --transform-translate-y: -2.5rem; + } + + .lg\:hover\:-translate-y-12:hover { + --transform-translate-y: -3rem; + } + + .lg\:hover\:-translate-y-16:hover { + --transform-translate-y: -4rem; + } + + .lg\:hover\:-translate-y-20:hover { + --transform-translate-y: -5rem; + } + + .lg\:hover\:-translate-y-24:hover { + --transform-translate-y: -6rem; + } + + .lg\:hover\:-translate-y-32:hover { + --transform-translate-y: -8rem; + } + + .lg\:hover\:-translate-y-40:hover { + --transform-translate-y: -10rem; + } + + .lg\:hover\:-translate-y-48:hover { + --transform-translate-y: -12rem; + } + + .lg\:hover\:-translate-y-56:hover { + --transform-translate-y: -14rem; + } + + .lg\:hover\:-translate-y-64:hover { + --transform-translate-y: -16rem; + } + + .lg\:hover\:-translate-y-px:hover { + --transform-translate-y: -1px; + } + + .lg\:hover\:-translate-y-full:hover { + --transform-translate-y: -100%; + } + + .lg\:hover\:-translate-y-1\/2:hover { + --transform-translate-y: -50%; + } + + .lg\:hover\:translate-y-1\/2:hover { + --transform-translate-y: 50%; + } + + .lg\:hover\:translate-y-full:hover { + --transform-translate-y: 100%; + } + + .lg\:focus\:translate-x-0:focus { + --transform-translate-x: 0; + } + + .lg\:focus\:translate-x-1:focus { + --transform-translate-x: 0.25rem; + } + + .lg\:focus\:translate-x-2:focus { + --transform-translate-x: 0.5rem; + } + + .lg\:focus\:translate-x-3:focus { + --transform-translate-x: 0.75rem; + } + + .lg\:focus\:translate-x-4:focus { + --transform-translate-x: 1rem; + } + + .lg\:focus\:translate-x-5:focus { + --transform-translate-x: 1.25rem; + } + + .lg\:focus\:translate-x-6:focus { + --transform-translate-x: 1.5rem; + } + + .lg\:focus\:translate-x-8:focus { + --transform-translate-x: 2rem; + } + + .lg\:focus\:translate-x-10:focus { + --transform-translate-x: 2.5rem; + } + + .lg\:focus\:translate-x-12:focus { + --transform-translate-x: 3rem; + } + + .lg\:focus\:translate-x-16:focus { + --transform-translate-x: 4rem; + } + + .lg\:focus\:translate-x-20:focus { + --transform-translate-x: 5rem; + } + + .lg\:focus\:translate-x-24:focus { + --transform-translate-x: 6rem; + } + + .lg\:focus\:translate-x-32:focus { + --transform-translate-x: 8rem; + } + + .lg\:focus\:translate-x-40:focus { + --transform-translate-x: 10rem; + } + + .lg\:focus\:translate-x-48:focus { + --transform-translate-x: 12rem; + } + + .lg\:focus\:translate-x-56:focus { + --transform-translate-x: 14rem; + } + + .lg\:focus\:translate-x-64:focus { + --transform-translate-x: 16rem; + } + + .lg\:focus\:translate-x-px:focus { + --transform-translate-x: 1px; + } + + .lg\:focus\:-translate-x-1:focus { + --transform-translate-x: -0.25rem; + } + + .lg\:focus\:-translate-x-2:focus { + --transform-translate-x: -0.5rem; + } + + .lg\:focus\:-translate-x-3:focus { + --transform-translate-x: -0.75rem; + } + + .lg\:focus\:-translate-x-4:focus { + --transform-translate-x: -1rem; + } + + .lg\:focus\:-translate-x-5:focus { + --transform-translate-x: -1.25rem; + } + + .lg\:focus\:-translate-x-6:focus { + --transform-translate-x: -1.5rem; + } + + .lg\:focus\:-translate-x-8:focus { + --transform-translate-x: -2rem; + } + + .lg\:focus\:-translate-x-10:focus { + --transform-translate-x: -2.5rem; + } + + .lg\:focus\:-translate-x-12:focus { + --transform-translate-x: -3rem; + } + + .lg\:focus\:-translate-x-16:focus { + --transform-translate-x: -4rem; + } + + .lg\:focus\:-translate-x-20:focus { + --transform-translate-x: -5rem; + } + + .lg\:focus\:-translate-x-24:focus { + --transform-translate-x: -6rem; + } + + .lg\:focus\:-translate-x-32:focus { + --transform-translate-x: -8rem; + } + + .lg\:focus\:-translate-x-40:focus { + --transform-translate-x: -10rem; + } + + .lg\:focus\:-translate-x-48:focus { + --transform-translate-x: -12rem; + } + + .lg\:focus\:-translate-x-56:focus { + --transform-translate-x: -14rem; + } + + .lg\:focus\:-translate-x-64:focus { + --transform-translate-x: -16rem; + } + + .lg\:focus\:-translate-x-px:focus { + --transform-translate-x: -1px; + } + + .lg\:focus\:-translate-x-full:focus { + --transform-translate-x: -100%; + } + + .lg\:focus\:-translate-x-1\/2:focus { + --transform-translate-x: -50%; + } + + .lg\:focus\:translate-x-1\/2:focus { + --transform-translate-x: 50%; + } + + .lg\:focus\:translate-x-full:focus { + --transform-translate-x: 100%; + } + + .lg\:focus\:translate-y-0:focus { + --transform-translate-y: 0; + } + + .lg\:focus\:translate-y-1:focus { + --transform-translate-y: 0.25rem; + } + + .lg\:focus\:translate-y-2:focus { + --transform-translate-y: 0.5rem; + } + + .lg\:focus\:translate-y-3:focus { + --transform-translate-y: 0.75rem; + } + + .lg\:focus\:translate-y-4:focus { + --transform-translate-y: 1rem; + } + + .lg\:focus\:translate-y-5:focus { + --transform-translate-y: 1.25rem; + } + + .lg\:focus\:translate-y-6:focus { + --transform-translate-y: 1.5rem; + } + + .lg\:focus\:translate-y-8:focus { + --transform-translate-y: 2rem; + } + + .lg\:focus\:translate-y-10:focus { + --transform-translate-y: 2.5rem; + } + + .lg\:focus\:translate-y-12:focus { + --transform-translate-y: 3rem; + } + + .lg\:focus\:translate-y-16:focus { + --transform-translate-y: 4rem; + } + + .lg\:focus\:translate-y-20:focus { + --transform-translate-y: 5rem; + } + + .lg\:focus\:translate-y-24:focus { + --transform-translate-y: 6rem; + } + + .lg\:focus\:translate-y-32:focus { + --transform-translate-y: 8rem; + } + + .lg\:focus\:translate-y-40:focus { + --transform-translate-y: 10rem; + } + + .lg\:focus\:translate-y-48:focus { + --transform-translate-y: 12rem; + } + + .lg\:focus\:translate-y-56:focus { + --transform-translate-y: 14rem; + } + + .lg\:focus\:translate-y-64:focus { + --transform-translate-y: 16rem; + } + + .lg\:focus\:translate-y-px:focus { + --transform-translate-y: 1px; + } + + .lg\:focus\:-translate-y-1:focus { + --transform-translate-y: -0.25rem; + } + + .lg\:focus\:-translate-y-2:focus { + --transform-translate-y: -0.5rem; + } + + .lg\:focus\:-translate-y-3:focus { + --transform-translate-y: -0.75rem; + } + + .lg\:focus\:-translate-y-4:focus { + --transform-translate-y: -1rem; + } + + .lg\:focus\:-translate-y-5:focus { + --transform-translate-y: -1.25rem; + } + + .lg\:focus\:-translate-y-6:focus { + --transform-translate-y: -1.5rem; + } + + .lg\:focus\:-translate-y-8:focus { + --transform-translate-y: -2rem; + } + + .lg\:focus\:-translate-y-10:focus { + --transform-translate-y: -2.5rem; + } + + .lg\:focus\:-translate-y-12:focus { + --transform-translate-y: -3rem; + } + + .lg\:focus\:-translate-y-16:focus { + --transform-translate-y: -4rem; + } + + .lg\:focus\:-translate-y-20:focus { + --transform-translate-y: -5rem; + } + + .lg\:focus\:-translate-y-24:focus { + --transform-translate-y: -6rem; + } + + .lg\:focus\:-translate-y-32:focus { + --transform-translate-y: -8rem; + } + + .lg\:focus\:-translate-y-40:focus { + --transform-translate-y: -10rem; + } + + .lg\:focus\:-translate-y-48:focus { + --transform-translate-y: -12rem; + } + + .lg\:focus\:-translate-y-56:focus { + --transform-translate-y: -14rem; + } + + .lg\:focus\:-translate-y-64:focus { + --transform-translate-y: -16rem; + } + + .lg\:focus\:-translate-y-px:focus { + --transform-translate-y: -1px; + } + + .lg\:focus\:-translate-y-full:focus { + --transform-translate-y: -100%; + } + + .lg\:focus\:-translate-y-1\/2:focus { + --transform-translate-y: -50%; + } + + .lg\:focus\:translate-y-1\/2:focus { + --transform-translate-y: 50%; + } + + .lg\:focus\:translate-y-full:focus { + --transform-translate-y: 100%; + } + + .lg\:skew-x-0 { + --transform-skew-x: 0; + } + + .lg\:skew-x-3 { + --transform-skew-x: 3deg; + } + + .lg\:skew-x-6 { + --transform-skew-x: 6deg; + } + + .lg\:skew-x-12 { + --transform-skew-x: 12deg; + } + + .lg\:-skew-x-12 { + --transform-skew-x: -12deg; + } + + .lg\:-skew-x-6 { + --transform-skew-x: -6deg; + } + + .lg\:-skew-x-3 { + --transform-skew-x: -3deg; + } + + .lg\:skew-y-0 { + --transform-skew-y: 0; + } + + .lg\:skew-y-3 { + --transform-skew-y: 3deg; + } + + .lg\:skew-y-6 { + --transform-skew-y: 6deg; + } + + .lg\:skew-y-12 { + --transform-skew-y: 12deg; + } + + .lg\:-skew-y-12 { + --transform-skew-y: -12deg; + } + + .lg\:-skew-y-6 { + --transform-skew-y: -6deg; + } + + .lg\:-skew-y-3 { + --transform-skew-y: -3deg; + } + + .lg\:hover\:skew-x-0:hover { + --transform-skew-x: 0; + } + + .lg\:hover\:skew-x-3:hover { + --transform-skew-x: 3deg; + } + + .lg\:hover\:skew-x-6:hover { + --transform-skew-x: 6deg; + } + + .lg\:hover\:skew-x-12:hover { + --transform-skew-x: 12deg; + } + + .lg\:hover\:-skew-x-12:hover { + --transform-skew-x: -12deg; + } + + .lg\:hover\:-skew-x-6:hover { + --transform-skew-x: -6deg; + } + + .lg\:hover\:-skew-x-3:hover { + --transform-skew-x: -3deg; + } + + .lg\:hover\:skew-y-0:hover { + --transform-skew-y: 0; + } + + .lg\:hover\:skew-y-3:hover { + --transform-skew-y: 3deg; + } + + .lg\:hover\:skew-y-6:hover { + --transform-skew-y: 6deg; + } + + .lg\:hover\:skew-y-12:hover { + --transform-skew-y: 12deg; + } + + .lg\:hover\:-skew-y-12:hover { + --transform-skew-y: -12deg; + } + + .lg\:hover\:-skew-y-6:hover { + --transform-skew-y: -6deg; + } + + .lg\:hover\:-skew-y-3:hover { + --transform-skew-y: -3deg; + } + + .lg\:focus\:skew-x-0:focus { + --transform-skew-x: 0; + } + + .lg\:focus\:skew-x-3:focus { + --transform-skew-x: 3deg; + } + + .lg\:focus\:skew-x-6:focus { + --transform-skew-x: 6deg; + } + + .lg\:focus\:skew-x-12:focus { + --transform-skew-x: 12deg; + } + + .lg\:focus\:-skew-x-12:focus { + --transform-skew-x: -12deg; + } + + .lg\:focus\:-skew-x-6:focus { + --transform-skew-x: -6deg; + } + + .lg\:focus\:-skew-x-3:focus { + --transform-skew-x: -3deg; + } + + .lg\:focus\:skew-y-0:focus { + --transform-skew-y: 0; + } + + .lg\:focus\:skew-y-3:focus { + --transform-skew-y: 3deg; + } + + .lg\:focus\:skew-y-6:focus { + --transform-skew-y: 6deg; + } + + .lg\:focus\:skew-y-12:focus { + --transform-skew-y: 12deg; + } + + .lg\:focus\:-skew-y-12:focus { + --transform-skew-y: -12deg; + } + + .lg\:focus\:-skew-y-6:focus { + --transform-skew-y: -6deg; + } + + .lg\:focus\:-skew-y-3:focus { + --transform-skew-y: -3deg; + } + + .lg\:transition-none { + transition-property: none; + } + + .lg\:transition-all { + transition-property: all; + } + + .lg\:transition { + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; + } + + .lg\:transition-colors { + transition-property: background-color, border-color, color, fill, stroke; + } + + .lg\:transition-opacity { + transition-property: opacity; + } + + .lg\:transition-shadow { + transition-property: box-shadow; + } + + .lg\:transition-transform { + transition-property: transform; + } + + .lg\:ease-linear { + transition-timing-function: linear; + } + + .lg\:ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); + } + + .lg\:ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + + .lg\:ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .lg\:duration-75 { + transition-duration: 75ms; + } + + .lg\:duration-100 { + transition-duration: 100ms; + } + + .lg\:duration-150 { + transition-duration: 150ms; + } + + .lg\:duration-200 { + transition-duration: 200ms; + } + + .lg\:duration-300 { + transition-duration: 300ms; + } + + .lg\:duration-500 { + transition-duration: 500ms; + } + + .lg\:duration-700 { + transition-duration: 700ms; + } + + .lg\:duration-1000 { + transition-duration: 1000ms; + } + + .lg\:delay-75 { + transition-delay: 75ms; + } + + .lg\:delay-100 { + transition-delay: 100ms; + } + + .lg\:delay-150 { + transition-delay: 150ms; + } + + .lg\:delay-200 { + transition-delay: 200ms; + } + + .lg\:delay-300 { + transition-delay: 300ms; + } + + .lg\:delay-500 { + transition-delay: 500ms; + } + + .lg\:delay-700 { + transition-delay: 700ms; + } + + .lg\:delay-1000 { + transition-delay: 1000ms; + } + + .lg\:animate-none { + -webkit-animation: none; + animation: none; + } + + .lg\:animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + } + + .lg\:animate-ping { + -webkit-animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + } + + .lg\:animate-pulse { + -webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + .lg\:animate-bounce { + -webkit-animation: bounce 1s infinite; + animation: bounce 1s infinite; + } +} + +@media (min-width: 1280px) { + .xl\:container { + width: 100%; + } + + @media (min-width: 640px) { + .xl\:container { + max-width: 640px; + } + } + + @media (min-width: 768px) { + .xl\:container { + max-width: 768px; + } + } + + @media (min-width: 1024px) { + .xl\:container { + max-width: 1024px; + } + } + + @media (min-width: 1280px) { + .xl\:container { + max-width: 1280px; + } + } + + .xl\:space-y-0 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0px * var(--space-y-reverse)); + } + + .xl\:space-x-0 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0px * var(--space-x-reverse)); + margin-left: calc(0px * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.25rem * var(--space-y-reverse)); + } + + .xl\:space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.25rem * var(--space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.5rem * var(--space-y-reverse)); + } + + .xl\:space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.5rem * var(--space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(0.75rem * var(--space-y-reverse)); + } + + .xl\:space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(0.75rem * var(--space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1rem * var(--space-y-reverse)); + } + + .xl\:space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1rem * var(--space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.25rem * var(--space-y-reverse)); + } + + .xl\:space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.25rem * var(--space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1.5rem * var(--space-y-reverse)); + } + + .xl\:space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1.5rem * var(--space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2rem * var(--space-y-reverse)); + } + + .xl\:space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2rem * var(--space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(2.5rem * var(--space-y-reverse)); + } + + .xl\:space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(2.5rem * var(--space-x-reverse)); + margin-left: calc(2.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(3rem * var(--space-y-reverse)); + } + + .xl\:space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(3rem * var(--space-x-reverse)); + margin-left: calc(3rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(4rem * var(--space-y-reverse)); + } + + .xl\:space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(4rem * var(--space-x-reverse)); + margin-left: calc(4rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(5rem * var(--space-y-reverse)); + } + + .xl\:space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(5rem * var(--space-x-reverse)); + margin-left: calc(5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(6rem * var(--space-y-reverse)); + } + + .xl\:space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(6rem * var(--space-x-reverse)); + margin-left: calc(6rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(8rem * var(--space-y-reverse)); + } + + .xl\:space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(8rem * var(--space-x-reverse)); + margin-left: calc(8rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(10rem * var(--space-y-reverse)); + } + + .xl\:space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(10rem * var(--space-x-reverse)); + margin-left: calc(10rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(12rem * var(--space-y-reverse)); + } + + .xl\:space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(12rem * var(--space-x-reverse)); + margin-left: calc(12rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(14rem * var(--space-y-reverse)); + } + + .xl\:space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(14rem * var(--space-x-reverse)); + margin-left: calc(14rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(16rem * var(--space-y-reverse)); + } + + .xl\:space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(16rem * var(--space-x-reverse)); + margin-left: calc(16rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(1px * var(--space-y-reverse)); + } + + .xl\:space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(1px * var(--space-x-reverse)); + margin-left: calc(1px * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-1 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.25rem * var(--space-y-reverse)); + } + + .xl\:-space-x-1 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.25rem * var(--space-x-reverse)); + margin-left: calc(-0.25rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-2 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.5rem * var(--space-y-reverse)); + } + + .xl\:-space-x-2 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.5rem * var(--space-x-reverse)); + margin-left: calc(-0.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-3 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-0.75rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-0.75rem * var(--space-y-reverse)); + } + + .xl\:-space-x-3 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-0.75rem * var(--space-x-reverse)); + margin-left: calc(-0.75rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-4 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1rem * var(--space-y-reverse)); + } + + .xl\:-space-x-4 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1rem * var(--space-x-reverse)); + margin-left: calc(-1rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-5 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.25rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.25rem * var(--space-y-reverse)); + } + + .xl\:-space-x-5 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.25rem * var(--space-x-reverse)); + margin-left: calc(-1.25rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-6 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1.5rem * var(--space-y-reverse)); + } + + .xl\:-space-x-6 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1.5rem * var(--space-x-reverse)); + margin-left: calc(-1.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-8 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2rem * var(--space-y-reverse)); + } + + .xl\:-space-x-8 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2rem * var(--space-x-reverse)); + margin-left: calc(-2rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-10 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-2.5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-2.5rem * var(--space-y-reverse)); + } + + .xl\:-space-x-10 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-2.5rem * var(--space-x-reverse)); + margin-left: calc(-2.5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-12 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-3rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-3rem * var(--space-y-reverse)); + } + + .xl\:-space-x-12 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-3rem * var(--space-x-reverse)); + margin-left: calc(-3rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-16 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-4rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-4rem * var(--space-y-reverse)); + } + + .xl\:-space-x-16 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-4rem * var(--space-x-reverse)); + margin-left: calc(-4rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-20 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-5rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-5rem * var(--space-y-reverse)); + } + + .xl\:-space-x-20 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-5rem * var(--space-x-reverse)); + margin-left: calc(-5rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-24 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-6rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-6rem * var(--space-y-reverse)); + } + + .xl\:-space-x-24 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-6rem * var(--space-x-reverse)); + margin-left: calc(-6rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-32 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-8rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-8rem * var(--space-y-reverse)); + } + + .xl\:-space-x-32 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-8rem * var(--space-x-reverse)); + margin-left: calc(-8rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-40 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-10rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-10rem * var(--space-y-reverse)); + } + + .xl\:-space-x-40 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-10rem * var(--space-x-reverse)); + margin-left: calc(-10rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-48 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-12rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-12rem * var(--space-y-reverse)); + } + + .xl\:-space-x-48 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-12rem * var(--space-x-reverse)); + margin-left: calc(-12rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-56 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-14rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-14rem * var(--space-y-reverse)); + } + + .xl\:-space-x-56 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-14rem * var(--space-x-reverse)); + margin-left: calc(-14rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-64 > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-16rem * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-16rem * var(--space-y-reverse)); + } + + .xl\:-space-x-64 > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-16rem * var(--space-x-reverse)); + margin-left: calc(-16rem * calc(1 - var(--space-x-reverse))); + } + + .xl\:-space-y-px > :not(template) ~ :not(template) { + --space-y-reverse: 0; + margin-top: calc(-1px * calc(1 - var(--space-y-reverse))); + margin-bottom: calc(-1px * var(--space-y-reverse)); + } + + .xl\:-space-x-px > :not(template) ~ :not(template) { + --space-x-reverse: 0; + margin-right: calc(-1px * var(--space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--space-x-reverse))); + } + + .xl\:space-y-reverse > :not(template) ~ :not(template) { + --space-y-reverse: 1; + } + + .xl\:space-x-reverse > :not(template) ~ :not(template) { + --space-x-reverse: 1; + } + + .xl\:divide-y-0 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(0px * var(--divide-y-reverse)); + } + + .xl\:divide-x-0 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(0px * var(--divide-x-reverse)); + border-left-width: calc(0px * calc(1 - var(--divide-x-reverse))); + } + + .xl\:divide-y-2 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(2px * var(--divide-y-reverse)); + } + + .xl\:divide-x-2 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(2px * var(--divide-x-reverse)); + border-left-width: calc(2px * calc(1 - var(--divide-x-reverse))); + } + + .xl\:divide-y-4 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(4px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(4px * var(--divide-y-reverse)); + } + + .xl\:divide-x-4 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(4px * var(--divide-x-reverse)); + border-left-width: calc(4px * calc(1 - var(--divide-x-reverse))); + } + + .xl\:divide-y-8 > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(8px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(8px * var(--divide-y-reverse)); + } + + .xl\:divide-x-8 > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(8px * var(--divide-x-reverse)); + border-left-width: calc(8px * calc(1 - var(--divide-x-reverse))); + } + + .xl\:divide-y > :not(template) ~ :not(template) { + --divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--divide-y-reverse))); + border-bottom-width: calc(1px * var(--divide-y-reverse)); + } + + .xl\:divide-x > :not(template) ~ :not(template) { + --divide-x-reverse: 0; + border-right-width: calc(1px * var(--divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--divide-x-reverse))); + } + + .xl\:divide-y-reverse > :not(template) ~ :not(template) { + --divide-y-reverse: 1; + } + + .xl\:divide-x-reverse > :not(template) ~ :not(template) { + --divide-x-reverse: 1; + } + + .xl\:divide-transparent > :not(template) ~ :not(template) { + border-color: transparent; + } + + .xl\:divide-current > :not(template) ~ :not(template) { + border-color: currentColor; + } + + .xl\:divide-black > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--divide-opacity)); + } + + .xl\:divide-white > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--divide-opacity)); + } + + .xl\:divide-gray-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--divide-opacity)); + } + + .xl\:divide-gray-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--divide-opacity)); + } + + .xl\:divide-gray-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--divide-opacity)); + } + + .xl\:divide-gray-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--divide-opacity)); + } + + .xl\:divide-gray-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--divide-opacity)); + } + + .xl\:divide-gray-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--divide-opacity)); + } + + .xl\:divide-gray-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--divide-opacity)); + } + + .xl\:divide-gray-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--divide-opacity)); + } + + .xl\:divide-gray-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--divide-opacity)); + } + + .xl\:divide-red-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--divide-opacity)); + } + + .xl\:divide-red-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--divide-opacity)); + } + + .xl\:divide-red-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--divide-opacity)); + } + + .xl\:divide-red-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--divide-opacity)); + } + + .xl\:divide-red-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--divide-opacity)); + } + + .xl\:divide-red-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--divide-opacity)); + } + + .xl\:divide-red-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--divide-opacity)); + } + + .xl\:divide-red-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--divide-opacity)); + } + + .xl\:divide-red-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--divide-opacity)); + } + + .xl\:divide-orange-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--divide-opacity)); + } + + .xl\:divide-orange-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--divide-opacity)); + } + + .xl\:divide-orange-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--divide-opacity)); + } + + .xl\:divide-orange-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--divide-opacity)); + } + + .xl\:divide-orange-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--divide-opacity)); + } + + .xl\:divide-orange-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--divide-opacity)); + } + + .xl\:divide-orange-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--divide-opacity)); + } + + .xl\:divide-orange-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--divide-opacity)); + } + + .xl\:divide-orange-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--divide-opacity)); + } + + .xl\:divide-yellow-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--divide-opacity)); + } + + .xl\:divide-yellow-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--divide-opacity)); + } + + .xl\:divide-yellow-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--divide-opacity)); + } + + .xl\:divide-yellow-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--divide-opacity)); + } + + .xl\:divide-yellow-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--divide-opacity)); + } + + .xl\:divide-yellow-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--divide-opacity)); + } + + .xl\:divide-yellow-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--divide-opacity)); + } + + .xl\:divide-yellow-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--divide-opacity)); + } + + .xl\:divide-yellow-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--divide-opacity)); + } + + .xl\:divide-green-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--divide-opacity)); + } + + .xl\:divide-green-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--divide-opacity)); + } + + .xl\:divide-green-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--divide-opacity)); + } + + .xl\:divide-green-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--divide-opacity)); + } + + .xl\:divide-green-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--divide-opacity)); + } + + .xl\:divide-green-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--divide-opacity)); + } + + .xl\:divide-green-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--divide-opacity)); + } + + .xl\:divide-green-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--divide-opacity)); + } + + .xl\:divide-green-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--divide-opacity)); + } + + .xl\:divide-teal-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--divide-opacity)); + } + + .xl\:divide-teal-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--divide-opacity)); + } + + .xl\:divide-teal-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--divide-opacity)); + } + + .xl\:divide-teal-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--divide-opacity)); + } + + .xl\:divide-teal-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--divide-opacity)); + } + + .xl\:divide-teal-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--divide-opacity)); + } + + .xl\:divide-teal-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--divide-opacity)); + } + + .xl\:divide-teal-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--divide-opacity)); + } + + .xl\:divide-teal-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--divide-opacity)); + } + + .xl\:divide-blue-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--divide-opacity)); + } + + .xl\:divide-blue-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--divide-opacity)); + } + + .xl\:divide-blue-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--divide-opacity)); + } + + .xl\:divide-blue-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--divide-opacity)); + } + + .xl\:divide-blue-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--divide-opacity)); + } + + .xl\:divide-blue-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--divide-opacity)); + } + + .xl\:divide-blue-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--divide-opacity)); + } + + .xl\:divide-blue-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--divide-opacity)); + } + + .xl\:divide-blue-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--divide-opacity)); + } + + .xl\:divide-indigo-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--divide-opacity)); + } + + .xl\:divide-indigo-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--divide-opacity)); + } + + .xl\:divide-indigo-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--divide-opacity)); + } + + .xl\:divide-indigo-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--divide-opacity)); + } + + .xl\:divide-indigo-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--divide-opacity)); + } + + .xl\:divide-indigo-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--divide-opacity)); + } + + .xl\:divide-indigo-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--divide-opacity)); + } + + .xl\:divide-indigo-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--divide-opacity)); + } + + .xl\:divide-indigo-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--divide-opacity)); + } + + .xl\:divide-purple-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--divide-opacity)); + } + + .xl\:divide-purple-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--divide-opacity)); + } + + .xl\:divide-purple-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--divide-opacity)); + } + + .xl\:divide-purple-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--divide-opacity)); + } + + .xl\:divide-purple-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--divide-opacity)); + } + + .xl\:divide-purple-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--divide-opacity)); + } + + .xl\:divide-purple-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--divide-opacity)); + } + + .xl\:divide-purple-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--divide-opacity)); + } + + .xl\:divide-purple-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--divide-opacity)); + } + + .xl\:divide-pink-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--divide-opacity)); + } + + .xl\:divide-pink-200 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--divide-opacity)); + } + + .xl\:divide-pink-300 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--divide-opacity)); + } + + .xl\:divide-pink-400 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--divide-opacity)); + } + + .xl\:divide-pink-500 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--divide-opacity)); + } + + .xl\:divide-pink-600 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--divide-opacity)); + } + + .xl\:divide-pink-700 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--divide-opacity)); + } + + .xl\:divide-pink-800 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--divide-opacity)); + } + + .xl\:divide-pink-900 > :not(template) ~ :not(template) { + --divide-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--divide-opacity)); + } + + .xl\:divide-solid > :not(template) ~ :not(template) { + border-style: solid; + } + + .xl\:divide-dashed > :not(template) ~ :not(template) { + border-style: dashed; + } + + .xl\:divide-dotted > :not(template) ~ :not(template) { + border-style: dotted; + } + + .xl\:divide-double > :not(template) ~ :not(template) { + border-style: double; + } + + .xl\:divide-none > :not(template) ~ :not(template) { + border-style: none; + } + + .xl\:divide-opacity-0 > :not(template) ~ :not(template) { + --divide-opacity: 0; + } + + .xl\:divide-opacity-25 > :not(template) ~ :not(template) { + --divide-opacity: 0.25; + } + + .xl\:divide-opacity-50 > :not(template) ~ :not(template) { + --divide-opacity: 0.5; + } + + .xl\:divide-opacity-75 > :not(template) ~ :not(template) { + --divide-opacity: 0.75; + } + + .xl\:divide-opacity-100 > :not(template) ~ :not(template) { + --divide-opacity: 1; + } + + .xl\:sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .xl\:not-sr-only { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .xl\:focus\:sr-only:focus { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + .xl\:focus\:not-sr-only:focus { + position: static; + width: auto; + height: auto; + padding: 0; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; + } + + .xl\:appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + .xl\:bg-fixed { + background-attachment: fixed; + } + + .xl\:bg-local { + background-attachment: local; + } + + .xl\:bg-scroll { + background-attachment: scroll; + } + + .xl\:bg-clip-border { + background-clip: border-box; + } + + .xl\:bg-clip-padding { + background-clip: padding-box; + } + + .xl\:bg-clip-content { + background-clip: content-box; + } + + .xl\:bg-clip-text { + -webkit-background-clip: text; + background-clip: text; + } + + .xl\:bg-transparent { + background-color: transparent; + } + + .xl\:bg-current { + background-color: currentColor; + } + + .xl\:bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .xl\:bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .xl\:bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .xl\:bg-gray-200 { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .xl\:bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .xl\:bg-gray-400 { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .xl\:bg-gray-500 { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .xl\:bg-gray-600 { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .xl\:bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .xl\:bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .xl\:bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .xl\:bg-red-100 { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .xl\:bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .xl\:bg-red-300 { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .xl\:bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .xl\:bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .xl\:bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .xl\:bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .xl\:bg-red-800 { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .xl\:bg-red-900 { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .xl\:bg-orange-100 { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .xl\:bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .xl\:bg-orange-300 { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .xl\:bg-orange-400 { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .xl\:bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .xl\:bg-orange-600 { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .xl\:bg-orange-700 { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .xl\:bg-orange-800 { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .xl\:bg-orange-900 { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .xl\:bg-yellow-100 { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .xl\:bg-yellow-200 { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .xl\:bg-yellow-300 { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .xl\:bg-yellow-400 { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .xl\:bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .xl\:bg-yellow-600 { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .xl\:bg-yellow-700 { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .xl\:bg-yellow-800 { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .xl\:bg-yellow-900 { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .xl\:bg-green-100 { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .xl\:bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .xl\:bg-green-300 { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .xl\:bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .xl\:bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .xl\:bg-green-600 { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .xl\:bg-green-700 { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .xl\:bg-green-800 { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .xl\:bg-green-900 { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .xl\:bg-teal-100 { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .xl\:bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .xl\:bg-teal-300 { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .xl\:bg-teal-400 { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .xl\:bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .xl\:bg-teal-600 { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .xl\:bg-teal-700 { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .xl\:bg-teal-800 { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .xl\:bg-teal-900 { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .xl\:bg-blue-100 { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .xl\:bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .xl\:bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .xl\:bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .xl\:bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .xl\:bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .xl\:bg-blue-700 { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .xl\:bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .xl\:bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .xl\:bg-indigo-100 { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .xl\:bg-indigo-200 { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .xl\:bg-indigo-300 { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .xl\:bg-indigo-400 { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .xl\:bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .xl\:bg-indigo-600 { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .xl\:bg-indigo-700 { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .xl\:bg-indigo-800 { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .xl\:bg-indigo-900 { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .xl\:bg-purple-100 { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .xl\:bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .xl\:bg-purple-300 { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .xl\:bg-purple-400 { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .xl\:bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .xl\:bg-purple-600 { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .xl\:bg-purple-700 { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .xl\:bg-purple-800 { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .xl\:bg-purple-900 { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .xl\:bg-pink-100 { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .xl\:bg-pink-200 { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .xl\:bg-pink-300 { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .xl\:bg-pink-400 { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .xl\:bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .xl\:bg-pink-600 { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .xl\:bg-pink-700 { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .xl\:bg-pink-800 { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .xl\:bg-pink-900 { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .xl\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .xl\:hover\:bg-current:hover { + background-color: currentColor; + } + + .xl\:hover\:bg-black:hover { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .xl\:hover\:bg-white:hover { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-100:hover { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-200:hover { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-300:hover { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-400:hover { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-500:hover { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-600:hover { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-700:hover { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-800:hover { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .xl\:hover\:bg-gray-900:hover { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-100:hover { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-200:hover { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-300:hover { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-400:hover { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-500:hover { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-600:hover { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-700:hover { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-800:hover { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .xl\:hover\:bg-red-900:hover { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-100:hover { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-200:hover { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-300:hover { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-400:hover { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-500:hover { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-600:hover { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-700:hover { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-800:hover { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .xl\:hover\:bg-orange-900:hover { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-100:hover { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-200:hover { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-300:hover { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-400:hover { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-500:hover { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-600:hover { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-700:hover { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-800:hover { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .xl\:hover\:bg-yellow-900:hover { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-100:hover { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-200:hover { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-300:hover { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-400:hover { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-500:hover { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-600:hover { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-700:hover { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-800:hover { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .xl\:hover\:bg-green-900:hover { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-100:hover { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-200:hover { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-300:hover { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-400:hover { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-500:hover { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-600:hover { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-700:hover { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-800:hover { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .xl\:hover\:bg-teal-900:hover { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-100:hover { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-200:hover { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-300:hover { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-400:hover { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-500:hover { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-600:hover { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-700:hover { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-800:hover { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .xl\:hover\:bg-blue-900:hover { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-100:hover { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-200:hover { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-300:hover { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-400:hover { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-500:hover { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-600:hover { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-700:hover { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-800:hover { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .xl\:hover\:bg-indigo-900:hover { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-100:hover { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-200:hover { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-300:hover { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-400:hover { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-500:hover { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-600:hover { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-700:hover { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-800:hover { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .xl\:hover\:bg-purple-900:hover { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-100:hover { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-200:hover { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-300:hover { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-400:hover { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-500:hover { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-600:hover { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-700:hover { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-800:hover { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .xl\:hover\:bg-pink-900:hover { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .xl\:focus\:bg-transparent:focus { + background-color: transparent; + } + + .xl\:focus\:bg-current:focus { + background-color: currentColor; + } + + .xl\:focus\:bg-black:focus { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); + } + + .xl\:focus\:bg-white:focus { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-100:focus { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-200:focus { + --bg-opacity: 1; + background-color: #edf2f7; + background-color: rgba(237, 242, 247, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-300:focus { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-400:focus { + --bg-opacity: 1; + background-color: #cbd5e0; + background-color: rgba(203, 213, 224, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-500:focus { + --bg-opacity: 1; + background-color: #a0aec0; + background-color: rgba(160, 174, 192, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-600:focus { + --bg-opacity: 1; + background-color: #718096; + background-color: rgba(113, 128, 150, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-700:focus { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-800:focus { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); + } + + .xl\:focus\:bg-gray-900:focus { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-100:focus { + --bg-opacity: 1; + background-color: #fff5f5; + background-color: rgba(255, 245, 245, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-200:focus { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-300:focus { + --bg-opacity: 1; + background-color: #feb2b2; + background-color: rgba(254, 178, 178, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-400:focus { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-500:focus { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-600:focus { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-700:focus { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-800:focus { + --bg-opacity: 1; + background-color: #9b2c2c; + background-color: rgba(155, 44, 44, var(--bg-opacity)); + } + + .xl\:focus\:bg-red-900:focus { + --bg-opacity: 1; + background-color: #742a2a; + background-color: rgba(116, 42, 42, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-100:focus { + --bg-opacity: 1; + background-color: #fffaf0; + background-color: rgba(255, 250, 240, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-200:focus { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-300:focus { + --bg-opacity: 1; + background-color: #fbd38d; + background-color: rgba(251, 211, 141, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-400:focus { + --bg-opacity: 1; + background-color: #f6ad55; + background-color: rgba(246, 173, 85, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-500:focus { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-600:focus { + --bg-opacity: 1; + background-color: #dd6b20; + background-color: rgba(221, 107, 32, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-700:focus { + --bg-opacity: 1; + background-color: #c05621; + background-color: rgba(192, 86, 33, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-800:focus { + --bg-opacity: 1; + background-color: #9c4221; + background-color: rgba(156, 66, 33, var(--bg-opacity)); + } + + .xl\:focus\:bg-orange-900:focus { + --bg-opacity: 1; + background-color: #7b341e; + background-color: rgba(123, 52, 30, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-100:focus { + --bg-opacity: 1; + background-color: #fffff0; + background-color: rgba(255, 255, 240, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-200:focus { + --bg-opacity: 1; + background-color: #fefcbf; + background-color: rgba(254, 252, 191, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-300:focus { + --bg-opacity: 1; + background-color: #faf089; + background-color: rgba(250, 240, 137, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-400:focus { + --bg-opacity: 1; + background-color: #f6e05e; + background-color: rgba(246, 224, 94, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-500:focus { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-600:focus { + --bg-opacity: 1; + background-color: #d69e2e; + background-color: rgba(214, 158, 46, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-700:focus { + --bg-opacity: 1; + background-color: #b7791f; + background-color: rgba(183, 121, 31, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-800:focus { + --bg-opacity: 1; + background-color: #975a16; + background-color: rgba(151, 90, 22, var(--bg-opacity)); + } + + .xl\:focus\:bg-yellow-900:focus { + --bg-opacity: 1; + background-color: #744210; + background-color: rgba(116, 66, 16, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-100:focus { + --bg-opacity: 1; + background-color: #f0fff4; + background-color: rgba(240, 255, 244, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-200:focus { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-300:focus { + --bg-opacity: 1; + background-color: #9ae6b4; + background-color: rgba(154, 230, 180, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-400:focus { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-500:focus { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-600:focus { + --bg-opacity: 1; + background-color: #38a169; + background-color: rgba(56, 161, 105, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-700:focus { + --bg-opacity: 1; + background-color: #2f855a; + background-color: rgba(47, 133, 90, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-800:focus { + --bg-opacity: 1; + background-color: #276749; + background-color: rgba(39, 103, 73, var(--bg-opacity)); + } + + .xl\:focus\:bg-green-900:focus { + --bg-opacity: 1; + background-color: #22543d; + background-color: rgba(34, 84, 61, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-100:focus { + --bg-opacity: 1; + background-color: #e6fffa; + background-color: rgba(230, 255, 250, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-200:focus { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-300:focus { + --bg-opacity: 1; + background-color: #81e6d9; + background-color: rgba(129, 230, 217, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-400:focus { + --bg-opacity: 1; + background-color: #4fd1c5; + background-color: rgba(79, 209, 197, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-500:focus { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-600:focus { + --bg-opacity: 1; + background-color: #319795; + background-color: rgba(49, 151, 149, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-700:focus { + --bg-opacity: 1; + background-color: #2c7a7b; + background-color: rgba(44, 122, 123, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-800:focus { + --bg-opacity: 1; + background-color: #285e61; + background-color: rgba(40, 94, 97, var(--bg-opacity)); + } + + .xl\:focus\:bg-teal-900:focus { + --bg-opacity: 1; + background-color: #234e52; + background-color: rgba(35, 78, 82, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-100:focus { + --bg-opacity: 1; + background-color: #ebf8ff; + background-color: rgba(235, 248, 255, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-200:focus { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-300:focus { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-400:focus { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-500:focus { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-600:focus { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-700:focus { + --bg-opacity: 1; + background-color: #2b6cb0; + background-color: rgba(43, 108, 176, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-800:focus { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); + } + + .xl\:focus\:bg-blue-900:focus { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-100:focus { + --bg-opacity: 1; + background-color: #ebf4ff; + background-color: rgba(235, 244, 255, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-200:focus { + --bg-opacity: 1; + background-color: #c3dafe; + background-color: rgba(195, 218, 254, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-300:focus { + --bg-opacity: 1; + background-color: #a3bffa; + background-color: rgba(163, 191, 250, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-400:focus { + --bg-opacity: 1; + background-color: #7f9cf5; + background-color: rgba(127, 156, 245, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-500:focus { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-600:focus { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-700:focus { + --bg-opacity: 1; + background-color: #4c51bf; + background-color: rgba(76, 81, 191, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-800:focus { + --bg-opacity: 1; + background-color: #434190; + background-color: rgba(67, 65, 144, var(--bg-opacity)); + } + + .xl\:focus\:bg-indigo-900:focus { + --bg-opacity: 1; + background-color: #3c366b; + background-color: rgba(60, 54, 107, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-100:focus { + --bg-opacity: 1; + background-color: #faf5ff; + background-color: rgba(250, 245, 255, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-200:focus { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-300:focus { + --bg-opacity: 1; + background-color: #d6bcfa; + background-color: rgba(214, 188, 250, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-400:focus { + --bg-opacity: 1; + background-color: #b794f4; + background-color: rgba(183, 148, 244, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-500:focus { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-600:focus { + --bg-opacity: 1; + background-color: #805ad5; + background-color: rgba(128, 90, 213, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-700:focus { + --bg-opacity: 1; + background-color: #6b46c1; + background-color: rgba(107, 70, 193, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-800:focus { + --bg-opacity: 1; + background-color: #553c9a; + background-color: rgba(85, 60, 154, var(--bg-opacity)); + } + + .xl\:focus\:bg-purple-900:focus { + --bg-opacity: 1; + background-color: #44337a; + background-color: rgba(68, 51, 122, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-100:focus { + --bg-opacity: 1; + background-color: #fff5f7; + background-color: rgba(255, 245, 247, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-200:focus { + --bg-opacity: 1; + background-color: #fed7e2; + background-color: rgba(254, 215, 226, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-300:focus { + --bg-opacity: 1; + background-color: #fbb6ce; + background-color: rgba(251, 182, 206, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-400:focus { + --bg-opacity: 1; + background-color: #f687b3; + background-color: rgba(246, 135, 179, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-500:focus { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-600:focus { + --bg-opacity: 1; + background-color: #d53f8c; + background-color: rgba(213, 63, 140, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-700:focus { + --bg-opacity: 1; + background-color: #b83280; + background-color: rgba(184, 50, 128, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-800:focus { + --bg-opacity: 1; + background-color: #97266d; + background-color: rgba(151, 38, 109, var(--bg-opacity)); + } + + .xl\:focus\:bg-pink-900:focus { + --bg-opacity: 1; + background-color: #702459; + background-color: rgba(112, 36, 89, var(--bg-opacity)); + } + + .xl\:bg-none { + background-image: none; + } + + .xl\:bg-gradient-to-t { + background-image: linear-gradient(to top, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-r { + background-image: linear-gradient(to right, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-l { + background-image: linear-gradient(to left, var(--gradient-color-stops)); + } + + .xl\:bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--gradient-color-stops)); + } + + .xl\:from-transparent { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:from-current { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:from-black { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:from-white { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:from-gray-100 { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:from-gray-200 { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:from-gray-300 { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:from-gray-400 { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:from-gray-500 { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:from-gray-600 { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:from-gray-700 { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:from-gray-800 { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:from-gray-900 { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:from-red-100 { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:from-red-200 { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:from-red-300 { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:from-red-400 { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:from-red-500 { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:from-red-600 { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:from-red-700 { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:from-red-800 { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:from-red-900 { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:from-orange-100 { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:from-orange-200 { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:from-orange-300 { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:from-orange-400 { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:from-orange-500 { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:from-orange-600 { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:from-orange-700 { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:from-orange-800 { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:from-orange-900 { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:from-yellow-100 { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:from-yellow-200 { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:from-yellow-300 { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:from-yellow-400 { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:from-yellow-500 { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:from-yellow-600 { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:from-yellow-700 { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:from-yellow-800 { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:from-yellow-900 { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:from-green-100 { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:from-green-200 { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:from-green-300 { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:from-green-400 { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:from-green-500 { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:from-green-600 { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:from-green-700 { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:from-green-800 { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:from-green-900 { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:from-teal-100 { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:from-teal-200 { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:from-teal-300 { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:from-teal-400 { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:from-teal-500 { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:from-teal-600 { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:from-teal-700 { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:from-teal-800 { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:from-teal-900 { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:from-blue-100 { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:from-blue-200 { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:from-blue-300 { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:from-blue-400 { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:from-blue-500 { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:from-blue-600 { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:from-blue-700 { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:from-blue-800 { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:from-blue-900 { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:from-indigo-100 { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:from-indigo-200 { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:from-indigo-300 { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:from-indigo-400 { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:from-indigo-500 { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:from-indigo-600 { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:from-indigo-700 { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:from-indigo-800 { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:from-indigo-900 { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:from-purple-100 { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:from-purple-200 { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:from-purple-300 { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:from-purple-400 { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:from-purple-500 { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:from-purple-600 { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:from-purple-700 { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:from-purple-800 { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:from-purple-900 { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:from-pink-100 { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:from-pink-200 { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:from-pink-300 { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:from-pink-400 { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:from-pink-500 { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:from-pink-600 { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:from-pink-700 { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:from-pink-800 { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:from-pink-900 { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:via-transparent { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:via-current { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:via-black { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:via-white { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:via-gray-100 { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:via-gray-200 { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:via-gray-300 { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:via-gray-400 { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:via-gray-500 { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:via-gray-600 { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:via-gray-700 { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:via-gray-800 { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:via-gray-900 { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:via-red-100 { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:via-red-200 { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:via-red-300 { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:via-red-400 { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:via-red-500 { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:via-red-600 { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:via-red-700 { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:via-red-800 { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:via-red-900 { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:via-orange-100 { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:via-orange-200 { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:via-orange-300 { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:via-orange-400 { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:via-orange-500 { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:via-orange-600 { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:via-orange-700 { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:via-orange-800 { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:via-orange-900 { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:via-yellow-100 { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:via-yellow-200 { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:via-yellow-300 { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:via-yellow-400 { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:via-yellow-500 { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:via-yellow-600 { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:via-yellow-700 { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:via-yellow-800 { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:via-yellow-900 { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:via-green-100 { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:via-green-200 { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:via-green-300 { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:via-green-400 { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:via-green-500 { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:via-green-600 { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:via-green-700 { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:via-green-800 { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:via-green-900 { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:via-teal-100 { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:via-teal-200 { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:via-teal-300 { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:via-teal-400 { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:via-teal-500 { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:via-teal-600 { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:via-teal-700 { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:via-teal-800 { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:via-teal-900 { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:via-blue-100 { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:via-blue-200 { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:via-blue-300 { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:via-blue-400 { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:via-blue-500 { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:via-blue-600 { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:via-blue-700 { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:via-blue-800 { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:via-blue-900 { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:via-indigo-100 { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:via-indigo-200 { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:via-indigo-300 { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:via-indigo-400 { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:via-indigo-500 { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:via-indigo-600 { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:via-indigo-700 { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:via-indigo-800 { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:via-indigo-900 { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:via-purple-100 { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:via-purple-200 { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:via-purple-300 { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:via-purple-400 { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:via-purple-500 { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:via-purple-600 { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:via-purple-700 { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:via-purple-800 { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:via-purple-900 { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:via-pink-100 { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:via-pink-200 { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:via-pink-300 { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:via-pink-400 { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:via-pink-500 { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:via-pink-600 { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:via-pink-700 { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:via-pink-800 { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:via-pink-900 { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:to-transparent { + --gradient-to-color: transparent; + } + + .xl\:to-current { + --gradient-to-color: currentColor; + } + + .xl\:to-black { + --gradient-to-color: #000; + } + + .xl\:to-white { + --gradient-to-color: #fff; + } + + .xl\:to-gray-100 { + --gradient-to-color: #f7fafc; + } + + .xl\:to-gray-200 { + --gradient-to-color: #edf2f7; + } + + .xl\:to-gray-300 { + --gradient-to-color: #e2e8f0; + } + + .xl\:to-gray-400 { + --gradient-to-color: #cbd5e0; + } + + .xl\:to-gray-500 { + --gradient-to-color: #a0aec0; + } + + .xl\:to-gray-600 { + --gradient-to-color: #718096; + } + + .xl\:to-gray-700 { + --gradient-to-color: #4a5568; + } + + .xl\:to-gray-800 { + --gradient-to-color: #2d3748; + } + + .xl\:to-gray-900 { + --gradient-to-color: #1a202c; + } + + .xl\:to-red-100 { + --gradient-to-color: #fff5f5; + } + + .xl\:to-red-200 { + --gradient-to-color: #fed7d7; + } + + .xl\:to-red-300 { + --gradient-to-color: #feb2b2; + } + + .xl\:to-red-400 { + --gradient-to-color: #fc8181; + } + + .xl\:to-red-500 { + --gradient-to-color: #f56565; + } + + .xl\:to-red-600 { + --gradient-to-color: #e53e3e; + } + + .xl\:to-red-700 { + --gradient-to-color: #c53030; + } + + .xl\:to-red-800 { + --gradient-to-color: #9b2c2c; + } + + .xl\:to-red-900 { + --gradient-to-color: #742a2a; + } + + .xl\:to-orange-100 { + --gradient-to-color: #fffaf0; + } + + .xl\:to-orange-200 { + --gradient-to-color: #feebc8; + } + + .xl\:to-orange-300 { + --gradient-to-color: #fbd38d; + } + + .xl\:to-orange-400 { + --gradient-to-color: #f6ad55; + } + + .xl\:to-orange-500 { + --gradient-to-color: #ed8936; + } + + .xl\:to-orange-600 { + --gradient-to-color: #dd6b20; + } + + .xl\:to-orange-700 { + --gradient-to-color: #c05621; + } + + .xl\:to-orange-800 { + --gradient-to-color: #9c4221; + } + + .xl\:to-orange-900 { + --gradient-to-color: #7b341e; + } + + .xl\:to-yellow-100 { + --gradient-to-color: #fffff0; + } + + .xl\:to-yellow-200 { + --gradient-to-color: #fefcbf; + } + + .xl\:to-yellow-300 { + --gradient-to-color: #faf089; + } + + .xl\:to-yellow-400 { + --gradient-to-color: #f6e05e; + } + + .xl\:to-yellow-500 { + --gradient-to-color: #ecc94b; + } + + .xl\:to-yellow-600 { + --gradient-to-color: #d69e2e; + } + + .xl\:to-yellow-700 { + --gradient-to-color: #b7791f; + } + + .xl\:to-yellow-800 { + --gradient-to-color: #975a16; + } + + .xl\:to-yellow-900 { + --gradient-to-color: #744210; + } + + .xl\:to-green-100 { + --gradient-to-color: #f0fff4; + } + + .xl\:to-green-200 { + --gradient-to-color: #c6f6d5; + } + + .xl\:to-green-300 { + --gradient-to-color: #9ae6b4; + } + + .xl\:to-green-400 { + --gradient-to-color: #68d391; + } + + .xl\:to-green-500 { + --gradient-to-color: #48bb78; + } + + .xl\:to-green-600 { + --gradient-to-color: #38a169; + } + + .xl\:to-green-700 { + --gradient-to-color: #2f855a; + } + + .xl\:to-green-800 { + --gradient-to-color: #276749; + } + + .xl\:to-green-900 { + --gradient-to-color: #22543d; + } + + .xl\:to-teal-100 { + --gradient-to-color: #e6fffa; + } + + .xl\:to-teal-200 { + --gradient-to-color: #b2f5ea; + } + + .xl\:to-teal-300 { + --gradient-to-color: #81e6d9; + } + + .xl\:to-teal-400 { + --gradient-to-color: #4fd1c5; + } + + .xl\:to-teal-500 { + --gradient-to-color: #38b2ac; + } + + .xl\:to-teal-600 { + --gradient-to-color: #319795; + } + + .xl\:to-teal-700 { + --gradient-to-color: #2c7a7b; + } + + .xl\:to-teal-800 { + --gradient-to-color: #285e61; + } + + .xl\:to-teal-900 { + --gradient-to-color: #234e52; + } + + .xl\:to-blue-100 { + --gradient-to-color: #ebf8ff; + } + + .xl\:to-blue-200 { + --gradient-to-color: #bee3f8; + } + + .xl\:to-blue-300 { + --gradient-to-color: #90cdf4; + } + + .xl\:to-blue-400 { + --gradient-to-color: #63b3ed; + } + + .xl\:to-blue-500 { + --gradient-to-color: #4299e1; + } + + .xl\:to-blue-600 { + --gradient-to-color: #3182ce; + } + + .xl\:to-blue-700 { + --gradient-to-color: #2b6cb0; + } + + .xl\:to-blue-800 { + --gradient-to-color: #2c5282; + } + + .xl\:to-blue-900 { + --gradient-to-color: #2a4365; + } + + .xl\:to-indigo-100 { + --gradient-to-color: #ebf4ff; + } + + .xl\:to-indigo-200 { + --gradient-to-color: #c3dafe; + } + + .xl\:to-indigo-300 { + --gradient-to-color: #a3bffa; + } + + .xl\:to-indigo-400 { + --gradient-to-color: #7f9cf5; + } + + .xl\:to-indigo-500 { + --gradient-to-color: #667eea; + } + + .xl\:to-indigo-600 { + --gradient-to-color: #5a67d8; + } + + .xl\:to-indigo-700 { + --gradient-to-color: #4c51bf; + } + + .xl\:to-indigo-800 { + --gradient-to-color: #434190; + } + + .xl\:to-indigo-900 { + --gradient-to-color: #3c366b; + } + + .xl\:to-purple-100 { + --gradient-to-color: #faf5ff; + } + + .xl\:to-purple-200 { + --gradient-to-color: #e9d8fd; + } + + .xl\:to-purple-300 { + --gradient-to-color: #d6bcfa; + } + + .xl\:to-purple-400 { + --gradient-to-color: #b794f4; + } + + .xl\:to-purple-500 { + --gradient-to-color: #9f7aea; + } + + .xl\:to-purple-600 { + --gradient-to-color: #805ad5; + } + + .xl\:to-purple-700 { + --gradient-to-color: #6b46c1; + } + + .xl\:to-purple-800 { + --gradient-to-color: #553c9a; + } + + .xl\:to-purple-900 { + --gradient-to-color: #44337a; + } + + .xl\:to-pink-100 { + --gradient-to-color: #fff5f7; + } + + .xl\:to-pink-200 { + --gradient-to-color: #fed7e2; + } + + .xl\:to-pink-300 { + --gradient-to-color: #fbb6ce; + } + + .xl\:to-pink-400 { + --gradient-to-color: #f687b3; + } + + .xl\:to-pink-500 { + --gradient-to-color: #ed64a6; + } + + .xl\:to-pink-600 { + --gradient-to-color: #d53f8c; + } + + .xl\:to-pink-700 { + --gradient-to-color: #b83280; + } + + .xl\:to-pink-800 { + --gradient-to-color: #97266d; + } + + .xl\:to-pink-900 { + --gradient-to-color: #702459; + } + + .xl\:hover\:from-transparent:hover { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:hover\:from-current:hover { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:hover\:from-black:hover { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:hover\:from-white:hover { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:hover\:from-gray-100:hover { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:hover\:from-gray-200:hover { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:hover\:from-gray-300:hover { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:hover\:from-gray-400:hover { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:hover\:from-gray-500:hover { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:hover\:from-gray-600:hover { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:hover\:from-gray-700:hover { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:hover\:from-gray-800:hover { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:hover\:from-gray-900:hover { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:hover\:from-red-100:hover { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:hover\:from-red-200:hover { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:hover\:from-red-300:hover { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:hover\:from-red-400:hover { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:hover\:from-red-500:hover { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:hover\:from-red-600:hover { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:hover\:from-red-700:hover { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:hover\:from-red-800:hover { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:hover\:from-red-900:hover { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:hover\:from-orange-100:hover { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:hover\:from-orange-200:hover { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:hover\:from-orange-300:hover { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:hover\:from-orange-400:hover { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:hover\:from-orange-500:hover { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:hover\:from-orange-600:hover { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:hover\:from-orange-700:hover { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:hover\:from-orange-800:hover { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:hover\:from-orange-900:hover { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:hover\:from-yellow-100:hover { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:hover\:from-yellow-200:hover { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:hover\:from-yellow-300:hover { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:hover\:from-yellow-400:hover { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:hover\:from-yellow-500:hover { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:hover\:from-yellow-600:hover { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:hover\:from-yellow-700:hover { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:hover\:from-yellow-800:hover { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:hover\:from-yellow-900:hover { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:hover\:from-green-100:hover { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:hover\:from-green-200:hover { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:hover\:from-green-300:hover { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:hover\:from-green-400:hover { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:hover\:from-green-500:hover { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:hover\:from-green-600:hover { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:hover\:from-green-700:hover { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:hover\:from-green-800:hover { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:hover\:from-green-900:hover { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:hover\:from-teal-100:hover { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:hover\:from-teal-200:hover { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:hover\:from-teal-300:hover { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:hover\:from-teal-400:hover { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:hover\:from-teal-500:hover { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:hover\:from-teal-600:hover { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:hover\:from-teal-700:hover { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:hover\:from-teal-800:hover { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:hover\:from-teal-900:hover { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:hover\:from-blue-100:hover { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:hover\:from-blue-200:hover { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:hover\:from-blue-300:hover { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:hover\:from-blue-400:hover { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:hover\:from-blue-500:hover { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:hover\:from-blue-600:hover { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:hover\:from-blue-700:hover { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:hover\:from-blue-800:hover { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:hover\:from-blue-900:hover { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:hover\:from-indigo-100:hover { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:hover\:from-indigo-200:hover { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:hover\:from-indigo-300:hover { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:hover\:from-indigo-400:hover { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:hover\:from-indigo-500:hover { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:hover\:from-indigo-600:hover { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:hover\:from-indigo-700:hover { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:hover\:from-indigo-800:hover { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:hover\:from-indigo-900:hover { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:hover\:from-purple-100:hover { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:hover\:from-purple-200:hover { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:hover\:from-purple-300:hover { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:hover\:from-purple-400:hover { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:hover\:from-purple-500:hover { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:hover\:from-purple-600:hover { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:hover\:from-purple-700:hover { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:hover\:from-purple-800:hover { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:hover\:from-purple-900:hover { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:hover\:from-pink-100:hover { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:hover\:from-pink-200:hover { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:hover\:from-pink-300:hover { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:hover\:from-pink-400:hover { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:hover\:from-pink-500:hover { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:hover\:from-pink-600:hover { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:hover\:from-pink-700:hover { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:hover\:from-pink-800:hover { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:hover\:from-pink-900:hover { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:hover\:via-transparent:hover { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:hover\:via-current:hover { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:hover\:via-black:hover { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:hover\:via-white:hover { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:hover\:via-gray-100:hover { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:hover\:via-gray-200:hover { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:hover\:via-gray-300:hover { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:hover\:via-gray-400:hover { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:hover\:via-gray-500:hover { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:hover\:via-gray-600:hover { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:hover\:via-gray-700:hover { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:hover\:via-gray-800:hover { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:hover\:via-gray-900:hover { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:hover\:via-red-100:hover { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:hover\:via-red-200:hover { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:hover\:via-red-300:hover { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:hover\:via-red-400:hover { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:hover\:via-red-500:hover { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:hover\:via-red-600:hover { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:hover\:via-red-700:hover { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:hover\:via-red-800:hover { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:hover\:via-red-900:hover { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:hover\:via-orange-100:hover { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:hover\:via-orange-200:hover { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:hover\:via-orange-300:hover { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:hover\:via-orange-400:hover { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:hover\:via-orange-500:hover { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:hover\:via-orange-600:hover { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:hover\:via-orange-700:hover { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:hover\:via-orange-800:hover { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:hover\:via-orange-900:hover { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:hover\:via-yellow-100:hover { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:hover\:via-yellow-200:hover { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:hover\:via-yellow-300:hover { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:hover\:via-yellow-400:hover { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:hover\:via-yellow-500:hover { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:hover\:via-yellow-600:hover { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:hover\:via-yellow-700:hover { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:hover\:via-yellow-800:hover { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:hover\:via-yellow-900:hover { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:hover\:via-green-100:hover { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:hover\:via-green-200:hover { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:hover\:via-green-300:hover { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:hover\:via-green-400:hover { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:hover\:via-green-500:hover { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:hover\:via-green-600:hover { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:hover\:via-green-700:hover { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:hover\:via-green-800:hover { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:hover\:via-green-900:hover { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:hover\:via-teal-100:hover { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:hover\:via-teal-200:hover { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:hover\:via-teal-300:hover { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:hover\:via-teal-400:hover { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:hover\:via-teal-500:hover { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:hover\:via-teal-600:hover { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:hover\:via-teal-700:hover { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:hover\:via-teal-800:hover { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:hover\:via-teal-900:hover { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:hover\:via-blue-100:hover { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:hover\:via-blue-200:hover { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:hover\:via-blue-300:hover { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:hover\:via-blue-400:hover { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:hover\:via-blue-500:hover { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:hover\:via-blue-600:hover { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:hover\:via-blue-700:hover { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:hover\:via-blue-800:hover { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:hover\:via-blue-900:hover { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:hover\:via-indigo-100:hover { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:hover\:via-indigo-200:hover { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:hover\:via-indigo-300:hover { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:hover\:via-indigo-400:hover { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:hover\:via-indigo-500:hover { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:hover\:via-indigo-600:hover { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:hover\:via-indigo-700:hover { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:hover\:via-indigo-800:hover { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:hover\:via-indigo-900:hover { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:hover\:via-purple-100:hover { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:hover\:via-purple-200:hover { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:hover\:via-purple-300:hover { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:hover\:via-purple-400:hover { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:hover\:via-purple-500:hover { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:hover\:via-purple-600:hover { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:hover\:via-purple-700:hover { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:hover\:via-purple-800:hover { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:hover\:via-purple-900:hover { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:hover\:via-pink-100:hover { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:hover\:via-pink-200:hover { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:hover\:via-pink-300:hover { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:hover\:via-pink-400:hover { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:hover\:via-pink-500:hover { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:hover\:via-pink-600:hover { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:hover\:via-pink-700:hover { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:hover\:via-pink-800:hover { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:hover\:via-pink-900:hover { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:hover\:to-transparent:hover { + --gradient-to-color: transparent; + } + + .xl\:hover\:to-current:hover { + --gradient-to-color: currentColor; + } + + .xl\:hover\:to-black:hover { + --gradient-to-color: #000; + } + + .xl\:hover\:to-white:hover { + --gradient-to-color: #fff; + } + + .xl\:hover\:to-gray-100:hover { + --gradient-to-color: #f7fafc; + } + + .xl\:hover\:to-gray-200:hover { + --gradient-to-color: #edf2f7; + } + + .xl\:hover\:to-gray-300:hover { + --gradient-to-color: #e2e8f0; + } + + .xl\:hover\:to-gray-400:hover { + --gradient-to-color: #cbd5e0; + } + + .xl\:hover\:to-gray-500:hover { + --gradient-to-color: #a0aec0; + } + + .xl\:hover\:to-gray-600:hover { + --gradient-to-color: #718096; + } + + .xl\:hover\:to-gray-700:hover { + --gradient-to-color: #4a5568; + } + + .xl\:hover\:to-gray-800:hover { + --gradient-to-color: #2d3748; + } + + .xl\:hover\:to-gray-900:hover { + --gradient-to-color: #1a202c; + } + + .xl\:hover\:to-red-100:hover { + --gradient-to-color: #fff5f5; + } + + .xl\:hover\:to-red-200:hover { + --gradient-to-color: #fed7d7; + } + + .xl\:hover\:to-red-300:hover { + --gradient-to-color: #feb2b2; + } + + .xl\:hover\:to-red-400:hover { + --gradient-to-color: #fc8181; + } + + .xl\:hover\:to-red-500:hover { + --gradient-to-color: #f56565; + } + + .xl\:hover\:to-red-600:hover { + --gradient-to-color: #e53e3e; + } + + .xl\:hover\:to-red-700:hover { + --gradient-to-color: #c53030; + } + + .xl\:hover\:to-red-800:hover { + --gradient-to-color: #9b2c2c; + } + + .xl\:hover\:to-red-900:hover { + --gradient-to-color: #742a2a; + } + + .xl\:hover\:to-orange-100:hover { + --gradient-to-color: #fffaf0; + } + + .xl\:hover\:to-orange-200:hover { + --gradient-to-color: #feebc8; + } + + .xl\:hover\:to-orange-300:hover { + --gradient-to-color: #fbd38d; + } + + .xl\:hover\:to-orange-400:hover { + --gradient-to-color: #f6ad55; + } + + .xl\:hover\:to-orange-500:hover { + --gradient-to-color: #ed8936; + } + + .xl\:hover\:to-orange-600:hover { + --gradient-to-color: #dd6b20; + } + + .xl\:hover\:to-orange-700:hover { + --gradient-to-color: #c05621; + } + + .xl\:hover\:to-orange-800:hover { + --gradient-to-color: #9c4221; + } + + .xl\:hover\:to-orange-900:hover { + --gradient-to-color: #7b341e; + } + + .xl\:hover\:to-yellow-100:hover { + --gradient-to-color: #fffff0; + } + + .xl\:hover\:to-yellow-200:hover { + --gradient-to-color: #fefcbf; + } + + .xl\:hover\:to-yellow-300:hover { + --gradient-to-color: #faf089; + } + + .xl\:hover\:to-yellow-400:hover { + --gradient-to-color: #f6e05e; + } + + .xl\:hover\:to-yellow-500:hover { + --gradient-to-color: #ecc94b; + } + + .xl\:hover\:to-yellow-600:hover { + --gradient-to-color: #d69e2e; + } + + .xl\:hover\:to-yellow-700:hover { + --gradient-to-color: #b7791f; + } + + .xl\:hover\:to-yellow-800:hover { + --gradient-to-color: #975a16; + } + + .xl\:hover\:to-yellow-900:hover { + --gradient-to-color: #744210; + } + + .xl\:hover\:to-green-100:hover { + --gradient-to-color: #f0fff4; + } + + .xl\:hover\:to-green-200:hover { + --gradient-to-color: #c6f6d5; + } + + .xl\:hover\:to-green-300:hover { + --gradient-to-color: #9ae6b4; + } + + .xl\:hover\:to-green-400:hover { + --gradient-to-color: #68d391; + } + + .xl\:hover\:to-green-500:hover { + --gradient-to-color: #48bb78; + } + + .xl\:hover\:to-green-600:hover { + --gradient-to-color: #38a169; + } + + .xl\:hover\:to-green-700:hover { + --gradient-to-color: #2f855a; + } + + .xl\:hover\:to-green-800:hover { + --gradient-to-color: #276749; + } + + .xl\:hover\:to-green-900:hover { + --gradient-to-color: #22543d; + } + + .xl\:hover\:to-teal-100:hover { + --gradient-to-color: #e6fffa; + } + + .xl\:hover\:to-teal-200:hover { + --gradient-to-color: #b2f5ea; + } + + .xl\:hover\:to-teal-300:hover { + --gradient-to-color: #81e6d9; + } + + .xl\:hover\:to-teal-400:hover { + --gradient-to-color: #4fd1c5; + } + + .xl\:hover\:to-teal-500:hover { + --gradient-to-color: #38b2ac; + } + + .xl\:hover\:to-teal-600:hover { + --gradient-to-color: #319795; + } + + .xl\:hover\:to-teal-700:hover { + --gradient-to-color: #2c7a7b; + } + + .xl\:hover\:to-teal-800:hover { + --gradient-to-color: #285e61; + } + + .xl\:hover\:to-teal-900:hover { + --gradient-to-color: #234e52; + } + + .xl\:hover\:to-blue-100:hover { + --gradient-to-color: #ebf8ff; + } + + .xl\:hover\:to-blue-200:hover { + --gradient-to-color: #bee3f8; + } + + .xl\:hover\:to-blue-300:hover { + --gradient-to-color: #90cdf4; + } + + .xl\:hover\:to-blue-400:hover { + --gradient-to-color: #63b3ed; + } + + .xl\:hover\:to-blue-500:hover { + --gradient-to-color: #4299e1; + } + + .xl\:hover\:to-blue-600:hover { + --gradient-to-color: #3182ce; + } + + .xl\:hover\:to-blue-700:hover { + --gradient-to-color: #2b6cb0; + } + + .xl\:hover\:to-blue-800:hover { + --gradient-to-color: #2c5282; + } + + .xl\:hover\:to-blue-900:hover { + --gradient-to-color: #2a4365; + } + + .xl\:hover\:to-indigo-100:hover { + --gradient-to-color: #ebf4ff; + } + + .xl\:hover\:to-indigo-200:hover { + --gradient-to-color: #c3dafe; + } + + .xl\:hover\:to-indigo-300:hover { + --gradient-to-color: #a3bffa; + } + + .xl\:hover\:to-indigo-400:hover { + --gradient-to-color: #7f9cf5; + } + + .xl\:hover\:to-indigo-500:hover { + --gradient-to-color: #667eea; + } + + .xl\:hover\:to-indigo-600:hover { + --gradient-to-color: #5a67d8; + } + + .xl\:hover\:to-indigo-700:hover { + --gradient-to-color: #4c51bf; + } + + .xl\:hover\:to-indigo-800:hover { + --gradient-to-color: #434190; + } + + .xl\:hover\:to-indigo-900:hover { + --gradient-to-color: #3c366b; + } + + .xl\:hover\:to-purple-100:hover { + --gradient-to-color: #faf5ff; + } + + .xl\:hover\:to-purple-200:hover { + --gradient-to-color: #e9d8fd; + } + + .xl\:hover\:to-purple-300:hover { + --gradient-to-color: #d6bcfa; + } + + .xl\:hover\:to-purple-400:hover { + --gradient-to-color: #b794f4; + } + + .xl\:hover\:to-purple-500:hover { + --gradient-to-color: #9f7aea; + } + + .xl\:hover\:to-purple-600:hover { + --gradient-to-color: #805ad5; + } + + .xl\:hover\:to-purple-700:hover { + --gradient-to-color: #6b46c1; + } + + .xl\:hover\:to-purple-800:hover { + --gradient-to-color: #553c9a; + } + + .xl\:hover\:to-purple-900:hover { + --gradient-to-color: #44337a; + } + + .xl\:hover\:to-pink-100:hover { + --gradient-to-color: #fff5f7; + } + + .xl\:hover\:to-pink-200:hover { + --gradient-to-color: #fed7e2; + } + + .xl\:hover\:to-pink-300:hover { + --gradient-to-color: #fbb6ce; + } + + .xl\:hover\:to-pink-400:hover { + --gradient-to-color: #f687b3; + } + + .xl\:hover\:to-pink-500:hover { + --gradient-to-color: #ed64a6; + } + + .xl\:hover\:to-pink-600:hover { + --gradient-to-color: #d53f8c; + } + + .xl\:hover\:to-pink-700:hover { + --gradient-to-color: #b83280; + } + + .xl\:hover\:to-pink-800:hover { + --gradient-to-color: #97266d; + } + + .xl\:hover\:to-pink-900:hover { + --gradient-to-color: #702459; + } + + .xl\:focus\:from-transparent:focus { + --gradient-from-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:focus\:from-current:focus { + --gradient-from-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:focus\:from-black:focus { + --gradient-from-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:focus\:from-white:focus { + --gradient-from-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:focus\:from-gray-100:focus { + --gradient-from-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:focus\:from-gray-200:focus { + --gradient-from-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:focus\:from-gray-300:focus { + --gradient-from-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:focus\:from-gray-400:focus { + --gradient-from-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:focus\:from-gray-500:focus { + --gradient-from-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:focus\:from-gray-600:focus { + --gradient-from-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:focus\:from-gray-700:focus { + --gradient-from-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:focus\:from-gray-800:focus { + --gradient-from-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:focus\:from-gray-900:focus { + --gradient-from-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:focus\:from-red-100:focus { + --gradient-from-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:focus\:from-red-200:focus { + --gradient-from-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:focus\:from-red-300:focus { + --gradient-from-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:focus\:from-red-400:focus { + --gradient-from-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:focus\:from-red-500:focus { + --gradient-from-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:focus\:from-red-600:focus { + --gradient-from-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:focus\:from-red-700:focus { + --gradient-from-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:focus\:from-red-800:focus { + --gradient-from-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:focus\:from-red-900:focus { + --gradient-from-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:focus\:from-orange-100:focus { + --gradient-from-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:focus\:from-orange-200:focus { + --gradient-from-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:focus\:from-orange-300:focus { + --gradient-from-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:focus\:from-orange-400:focus { + --gradient-from-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:focus\:from-orange-500:focus { + --gradient-from-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:focus\:from-orange-600:focus { + --gradient-from-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:focus\:from-orange-700:focus { + --gradient-from-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:focus\:from-orange-800:focus { + --gradient-from-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:focus\:from-orange-900:focus { + --gradient-from-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:focus\:from-yellow-100:focus { + --gradient-from-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:focus\:from-yellow-200:focus { + --gradient-from-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:focus\:from-yellow-300:focus { + --gradient-from-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:focus\:from-yellow-400:focus { + --gradient-from-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:focus\:from-yellow-500:focus { + --gradient-from-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:focus\:from-yellow-600:focus { + --gradient-from-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:focus\:from-yellow-700:focus { + --gradient-from-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:focus\:from-yellow-800:focus { + --gradient-from-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:focus\:from-yellow-900:focus { + --gradient-from-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:focus\:from-green-100:focus { + --gradient-from-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:focus\:from-green-200:focus { + --gradient-from-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:focus\:from-green-300:focus { + --gradient-from-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:focus\:from-green-400:focus { + --gradient-from-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:focus\:from-green-500:focus { + --gradient-from-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:focus\:from-green-600:focus { + --gradient-from-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:focus\:from-green-700:focus { + --gradient-from-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:focus\:from-green-800:focus { + --gradient-from-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:focus\:from-green-900:focus { + --gradient-from-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:focus\:from-teal-100:focus { + --gradient-from-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:focus\:from-teal-200:focus { + --gradient-from-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:focus\:from-teal-300:focus { + --gradient-from-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:focus\:from-teal-400:focus { + --gradient-from-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:focus\:from-teal-500:focus { + --gradient-from-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:focus\:from-teal-600:focus { + --gradient-from-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:focus\:from-teal-700:focus { + --gradient-from-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:focus\:from-teal-800:focus { + --gradient-from-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:focus\:from-teal-900:focus { + --gradient-from-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:focus\:from-blue-100:focus { + --gradient-from-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:focus\:from-blue-200:focus { + --gradient-from-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:focus\:from-blue-300:focus { + --gradient-from-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:focus\:from-blue-400:focus { + --gradient-from-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:focus\:from-blue-500:focus { + --gradient-from-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:focus\:from-blue-600:focus { + --gradient-from-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:focus\:from-blue-700:focus { + --gradient-from-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:focus\:from-blue-800:focus { + --gradient-from-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:focus\:from-blue-900:focus { + --gradient-from-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:focus\:from-indigo-100:focus { + --gradient-from-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:focus\:from-indigo-200:focus { + --gradient-from-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:focus\:from-indigo-300:focus { + --gradient-from-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:focus\:from-indigo-400:focus { + --gradient-from-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:focus\:from-indigo-500:focus { + --gradient-from-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:focus\:from-indigo-600:focus { + --gradient-from-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:focus\:from-indigo-700:focus { + --gradient-from-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:focus\:from-indigo-800:focus { + --gradient-from-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:focus\:from-indigo-900:focus { + --gradient-from-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:focus\:from-purple-100:focus { + --gradient-from-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:focus\:from-purple-200:focus { + --gradient-from-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:focus\:from-purple-300:focus { + --gradient-from-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:focus\:from-purple-400:focus { + --gradient-from-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:focus\:from-purple-500:focus { + --gradient-from-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:focus\:from-purple-600:focus { + --gradient-from-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:focus\:from-purple-700:focus { + --gradient-from-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:focus\:from-purple-800:focus { + --gradient-from-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:focus\:from-purple-900:focus { + --gradient-from-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:focus\:from-pink-100:focus { + --gradient-from-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:focus\:from-pink-200:focus { + --gradient-from-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:focus\:from-pink-300:focus { + --gradient-from-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:focus\:from-pink-400:focus { + --gradient-from-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:focus\:from-pink-500:focus { + --gradient-from-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:focus\:from-pink-600:focus { + --gradient-from-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:focus\:from-pink-700:focus { + --gradient-from-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:focus\:from-pink-800:focus { + --gradient-from-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:focus\:from-pink-900:focus { + --gradient-from-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:focus\:via-transparent:focus { + --gradient-via-color: transparent; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:focus\:via-current:focus { + --gradient-via-color: currentColor; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:focus\:via-black:focus { + --gradient-via-color: #000; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(0, 0, 0, 0)); + } + + .xl\:focus\:via-white:focus { + --gradient-via-color: #fff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0)); + } + + .xl\:focus\:via-gray-100:focus { + --gradient-via-color: #f7fafc; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(247, 250, 252, 0)); + } + + .xl\:focus\:via-gray-200:focus { + --gradient-via-color: #edf2f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 242, 247, 0)); + } + + .xl\:focus\:via-gray-300:focus { + --gradient-via-color: #e2e8f0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(226, 232, 240, 0)); + } + + .xl\:focus\:via-gray-400:focus { + --gradient-via-color: #cbd5e0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(203, 213, 224, 0)); + } + + .xl\:focus\:via-gray-500:focus { + --gradient-via-color: #a0aec0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(160, 174, 192, 0)); + } + + .xl\:focus\:via-gray-600:focus { + --gradient-via-color: #718096; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(113, 128, 150, 0)); + } + + .xl\:focus\:via-gray-700:focus { + --gradient-via-color: #4a5568; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(74, 85, 104, 0)); + } + + .xl\:focus\:via-gray-800:focus { + --gradient-via-color: #2d3748; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(45, 55, 72, 0)); + } + + .xl\:focus\:via-gray-900:focus { + --gradient-via-color: #1a202c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(26, 32, 44, 0)); + } + + .xl\:focus\:via-red-100:focus { + --gradient-via-color: #fff5f5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 245, 0)); + } + + .xl\:focus\:via-red-200:focus { + --gradient-via-color: #fed7d7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 215, 0)); + } + + .xl\:focus\:via-red-300:focus { + --gradient-via-color: #feb2b2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 178, 178, 0)); + } + + .xl\:focus\:via-red-400:focus { + --gradient-via-color: #fc8181; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(252, 129, 129, 0)); + } + + .xl\:focus\:via-red-500:focus { + --gradient-via-color: #f56565; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(245, 101, 101, 0)); + } + + .xl\:focus\:via-red-600:focus { + --gradient-via-color: #e53e3e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(229, 62, 62, 0)); + } + + .xl\:focus\:via-red-700:focus { + --gradient-via-color: #c53030; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(197, 48, 48, 0)); + } + + .xl\:focus\:via-red-800:focus { + --gradient-via-color: #9b2c2c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(155, 44, 44, 0)); + } + + .xl\:focus\:via-red-900:focus { + --gradient-via-color: #742a2a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 42, 42, 0)); + } + + .xl\:focus\:via-orange-100:focus { + --gradient-via-color: #fffaf0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 250, 240, 0)); + } + + .xl\:focus\:via-orange-200:focus { + --gradient-via-color: #feebc8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 235, 200, 0)); + } + + .xl\:focus\:via-orange-300:focus { + --gradient-via-color: #fbd38d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 211, 141, 0)); + } + + .xl\:focus\:via-orange-400:focus { + --gradient-via-color: #f6ad55; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 173, 85, 0)); + } + + .xl\:focus\:via-orange-500:focus { + --gradient-via-color: #ed8936; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 137, 54, 0)); + } + + .xl\:focus\:via-orange-600:focus { + --gradient-via-color: #dd6b20; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(221, 107, 32, 0)); + } + + .xl\:focus\:via-orange-700:focus { + --gradient-via-color: #c05621; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(192, 86, 33, 0)); + } + + .xl\:focus\:via-orange-800:focus { + --gradient-via-color: #9c4221; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(156, 66, 33, 0)); + } + + .xl\:focus\:via-orange-900:focus { + --gradient-via-color: #7b341e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(123, 52, 30, 0)); + } + + .xl\:focus\:via-yellow-100:focus { + --gradient-via-color: #fffff0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 240, 0)); + } + + .xl\:focus\:via-yellow-200:focus { + --gradient-via-color: #fefcbf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 252, 191, 0)); + } + + .xl\:focus\:via-yellow-300:focus { + --gradient-via-color: #faf089; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 240, 137, 0)); + } + + .xl\:focus\:via-yellow-400:focus { + --gradient-via-color: #f6e05e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 224, 94, 0)); + } + + .xl\:focus\:via-yellow-500:focus { + --gradient-via-color: #ecc94b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(236, 201, 75, 0)); + } + + .xl\:focus\:via-yellow-600:focus { + --gradient-via-color: #d69e2e; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 158, 46, 0)); + } + + .xl\:focus\:via-yellow-700:focus { + --gradient-via-color: #b7791f; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 121, 31, 0)); + } + + .xl\:focus\:via-yellow-800:focus { + --gradient-via-color: #975a16; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 90, 22, 0)); + } + + .xl\:focus\:via-yellow-900:focus { + --gradient-via-color: #744210; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(116, 66, 16, 0)); + } + + .xl\:focus\:via-green-100:focus { + --gradient-via-color: #f0fff4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(240, 255, 244, 0)); + } + + .xl\:focus\:via-green-200:focus { + --gradient-via-color: #c6f6d5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(198, 246, 213, 0)); + } + + .xl\:focus\:via-green-300:focus { + --gradient-via-color: #9ae6b4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(154, 230, 180, 0)); + } + + .xl\:focus\:via-green-400:focus { + --gradient-via-color: #68d391; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(104, 211, 145, 0)); + } + + .xl\:focus\:via-green-500:focus { + --gradient-via-color: #48bb78; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(72, 187, 120, 0)); + } + + .xl\:focus\:via-green-600:focus { + --gradient-via-color: #38a169; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 161, 105, 0)); + } + + .xl\:focus\:via-green-700:focus { + --gradient-via-color: #2f855a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(47, 133, 90, 0)); + } + + .xl\:focus\:via-green-800:focus { + --gradient-via-color: #276749; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(39, 103, 73, 0)); + } + + .xl\:focus\:via-green-900:focus { + --gradient-via-color: #22543d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(34, 84, 61, 0)); + } + + .xl\:focus\:via-teal-100:focus { + --gradient-via-color: #e6fffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(230, 255, 250, 0)); + } + + .xl\:focus\:via-teal-200:focus { + --gradient-via-color: #b2f5ea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(178, 245, 234, 0)); + } + + .xl\:focus\:via-teal-300:focus { + --gradient-via-color: #81e6d9; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(129, 230, 217, 0)); + } + + .xl\:focus\:via-teal-400:focus { + --gradient-via-color: #4fd1c5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(79, 209, 197, 0)); + } + + .xl\:focus\:via-teal-500:focus { + --gradient-via-color: #38b2ac; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(56, 178, 172, 0)); + } + + .xl\:focus\:via-teal-600:focus { + --gradient-via-color: #319795; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 151, 149, 0)); + } + + .xl\:focus\:via-teal-700:focus { + --gradient-via-color: #2c7a7b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 122, 123, 0)); + } + + .xl\:focus\:via-teal-800:focus { + --gradient-via-color: #285e61; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(40, 94, 97, 0)); + } + + .xl\:focus\:via-teal-900:focus { + --gradient-via-color: #234e52; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(35, 78, 82, 0)); + } + + .xl\:focus\:via-blue-100:focus { + --gradient-via-color: #ebf8ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 248, 255, 0)); + } + + .xl\:focus\:via-blue-200:focus { + --gradient-via-color: #bee3f8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(190, 227, 248, 0)); + } + + .xl\:focus\:via-blue-300:focus { + --gradient-via-color: #90cdf4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(144, 205, 244, 0)); + } + + .xl\:focus\:via-blue-400:focus { + --gradient-via-color: #63b3ed; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(99, 179, 237, 0)); + } + + .xl\:focus\:via-blue-500:focus { + --gradient-via-color: #4299e1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(66, 153, 225, 0)); + } + + .xl\:focus\:via-blue-600:focus { + --gradient-via-color: #3182ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(49, 130, 206, 0)); + } + + .xl\:focus\:via-blue-700:focus { + --gradient-via-color: #2b6cb0; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(43, 108, 176, 0)); + } + + .xl\:focus\:via-blue-800:focus { + --gradient-via-color: #2c5282; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(44, 82, 130, 0)); + } + + .xl\:focus\:via-blue-900:focus { + --gradient-via-color: #2a4365; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(42, 67, 101, 0)); + } + + .xl\:focus\:via-indigo-100:focus { + --gradient-via-color: #ebf4ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(235, 244, 255, 0)); + } + + .xl\:focus\:via-indigo-200:focus { + --gradient-via-color: #c3dafe; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(195, 218, 254, 0)); + } + + .xl\:focus\:via-indigo-300:focus { + --gradient-via-color: #a3bffa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(163, 191, 250, 0)); + } + + .xl\:focus\:via-indigo-400:focus { + --gradient-via-color: #7f9cf5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(127, 156, 245, 0)); + } + + .xl\:focus\:via-indigo-500:focus { + --gradient-via-color: #667eea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(102, 126, 234, 0)); + } + + .xl\:focus\:via-indigo-600:focus { + --gradient-via-color: #5a67d8; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(90, 103, 216, 0)); + } + + .xl\:focus\:via-indigo-700:focus { + --gradient-via-color: #4c51bf; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(76, 81, 191, 0)); + } + + .xl\:focus\:via-indigo-800:focus { + --gradient-via-color: #434190; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(67, 65, 144, 0)); + } + + .xl\:focus\:via-indigo-900:focus { + --gradient-via-color: #3c366b; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(60, 54, 107, 0)); + } + + .xl\:focus\:via-purple-100:focus { + --gradient-via-color: #faf5ff; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(250, 245, 255, 0)); + } + + .xl\:focus\:via-purple-200:focus { + --gradient-via-color: #e9d8fd; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(233, 216, 253, 0)); + } + + .xl\:focus\:via-purple-300:focus { + --gradient-via-color: #d6bcfa; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(214, 188, 250, 0)); + } + + .xl\:focus\:via-purple-400:focus { + --gradient-via-color: #b794f4; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(183, 148, 244, 0)); + } + + .xl\:focus\:via-purple-500:focus { + --gradient-via-color: #9f7aea; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(159, 122, 234, 0)); + } + + .xl\:focus\:via-purple-600:focus { + --gradient-via-color: #805ad5; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(128, 90, 213, 0)); + } + + .xl\:focus\:via-purple-700:focus { + --gradient-via-color: #6b46c1; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(107, 70, 193, 0)); + } + + .xl\:focus\:via-purple-800:focus { + --gradient-via-color: #553c9a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(85, 60, 154, 0)); + } + + .xl\:focus\:via-purple-900:focus { + --gradient-via-color: #44337a; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(68, 51, 122, 0)); + } + + .xl\:focus\:via-pink-100:focus { + --gradient-via-color: #fff5f7; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 245, 247, 0)); + } + + .xl\:focus\:via-pink-200:focus { + --gradient-via-color: #fed7e2; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(254, 215, 226, 0)); + } + + .xl\:focus\:via-pink-300:focus { + --gradient-via-color: #fbb6ce; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(251, 182, 206, 0)); + } + + .xl\:focus\:via-pink-400:focus { + --gradient-via-color: #f687b3; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(246, 135, 179, 0)); + } + + .xl\:focus\:via-pink-500:focus { + --gradient-via-color: #ed64a6; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(237, 100, 166, 0)); + } + + .xl\:focus\:via-pink-600:focus { + --gradient-via-color: #d53f8c; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(213, 63, 140, 0)); + } + + .xl\:focus\:via-pink-700:focus { + --gradient-via-color: #b83280; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(184, 50, 128, 0)); + } + + .xl\:focus\:via-pink-800:focus { + --gradient-via-color: #97266d; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(151, 38, 109, 0)); + } + + .xl\:focus\:via-pink-900:focus { + --gradient-via-color: #702459; + --gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(112, 36, 89, 0)); + } + + .xl\:focus\:to-transparent:focus { + --gradient-to-color: transparent; + } + + .xl\:focus\:to-current:focus { + --gradient-to-color: currentColor; + } + + .xl\:focus\:to-black:focus { + --gradient-to-color: #000; + } + + .xl\:focus\:to-white:focus { + --gradient-to-color: #fff; + } + + .xl\:focus\:to-gray-100:focus { + --gradient-to-color: #f7fafc; + } + + .xl\:focus\:to-gray-200:focus { + --gradient-to-color: #edf2f7; + } + + .xl\:focus\:to-gray-300:focus { + --gradient-to-color: #e2e8f0; + } + + .xl\:focus\:to-gray-400:focus { + --gradient-to-color: #cbd5e0; + } + + .xl\:focus\:to-gray-500:focus { + --gradient-to-color: #a0aec0; + } + + .xl\:focus\:to-gray-600:focus { + --gradient-to-color: #718096; + } + + .xl\:focus\:to-gray-700:focus { + --gradient-to-color: #4a5568; + } + + .xl\:focus\:to-gray-800:focus { + --gradient-to-color: #2d3748; + } + + .xl\:focus\:to-gray-900:focus { + --gradient-to-color: #1a202c; + } + + .xl\:focus\:to-red-100:focus { + --gradient-to-color: #fff5f5; + } + + .xl\:focus\:to-red-200:focus { + --gradient-to-color: #fed7d7; + } + + .xl\:focus\:to-red-300:focus { + --gradient-to-color: #feb2b2; + } + + .xl\:focus\:to-red-400:focus { + --gradient-to-color: #fc8181; + } + + .xl\:focus\:to-red-500:focus { + --gradient-to-color: #f56565; + } + + .xl\:focus\:to-red-600:focus { + --gradient-to-color: #e53e3e; + } + + .xl\:focus\:to-red-700:focus { + --gradient-to-color: #c53030; + } + + .xl\:focus\:to-red-800:focus { + --gradient-to-color: #9b2c2c; + } + + .xl\:focus\:to-red-900:focus { + --gradient-to-color: #742a2a; + } + + .xl\:focus\:to-orange-100:focus { + --gradient-to-color: #fffaf0; + } + + .xl\:focus\:to-orange-200:focus { + --gradient-to-color: #feebc8; + } + + .xl\:focus\:to-orange-300:focus { + --gradient-to-color: #fbd38d; + } + + .xl\:focus\:to-orange-400:focus { + --gradient-to-color: #f6ad55; + } + + .xl\:focus\:to-orange-500:focus { + --gradient-to-color: #ed8936; + } + + .xl\:focus\:to-orange-600:focus { + --gradient-to-color: #dd6b20; + } + + .xl\:focus\:to-orange-700:focus { + --gradient-to-color: #c05621; + } + + .xl\:focus\:to-orange-800:focus { + --gradient-to-color: #9c4221; + } + + .xl\:focus\:to-orange-900:focus { + --gradient-to-color: #7b341e; + } + + .xl\:focus\:to-yellow-100:focus { + --gradient-to-color: #fffff0; + } + + .xl\:focus\:to-yellow-200:focus { + --gradient-to-color: #fefcbf; + } + + .xl\:focus\:to-yellow-300:focus { + --gradient-to-color: #faf089; + } + + .xl\:focus\:to-yellow-400:focus { + --gradient-to-color: #f6e05e; + } + + .xl\:focus\:to-yellow-500:focus { + --gradient-to-color: #ecc94b; + } + + .xl\:focus\:to-yellow-600:focus { + --gradient-to-color: #d69e2e; + } + + .xl\:focus\:to-yellow-700:focus { + --gradient-to-color: #b7791f; + } + + .xl\:focus\:to-yellow-800:focus { + --gradient-to-color: #975a16; + } + + .xl\:focus\:to-yellow-900:focus { + --gradient-to-color: #744210; + } + + .xl\:focus\:to-green-100:focus { + --gradient-to-color: #f0fff4; + } + + .xl\:focus\:to-green-200:focus { + --gradient-to-color: #c6f6d5; + } + + .xl\:focus\:to-green-300:focus { + --gradient-to-color: #9ae6b4; + } + + .xl\:focus\:to-green-400:focus { + --gradient-to-color: #68d391; + } + + .xl\:focus\:to-green-500:focus { + --gradient-to-color: #48bb78; + } + + .xl\:focus\:to-green-600:focus { + --gradient-to-color: #38a169; + } + + .xl\:focus\:to-green-700:focus { + --gradient-to-color: #2f855a; + } + + .xl\:focus\:to-green-800:focus { + --gradient-to-color: #276749; + } + + .xl\:focus\:to-green-900:focus { + --gradient-to-color: #22543d; + } + + .xl\:focus\:to-teal-100:focus { + --gradient-to-color: #e6fffa; + } + + .xl\:focus\:to-teal-200:focus { + --gradient-to-color: #b2f5ea; + } + + .xl\:focus\:to-teal-300:focus { + --gradient-to-color: #81e6d9; + } + + .xl\:focus\:to-teal-400:focus { + --gradient-to-color: #4fd1c5; + } + + .xl\:focus\:to-teal-500:focus { + --gradient-to-color: #38b2ac; + } + + .xl\:focus\:to-teal-600:focus { + --gradient-to-color: #319795; + } + + .xl\:focus\:to-teal-700:focus { + --gradient-to-color: #2c7a7b; + } + + .xl\:focus\:to-teal-800:focus { + --gradient-to-color: #285e61; + } + + .xl\:focus\:to-teal-900:focus { + --gradient-to-color: #234e52; + } + + .xl\:focus\:to-blue-100:focus { + --gradient-to-color: #ebf8ff; + } + + .xl\:focus\:to-blue-200:focus { + --gradient-to-color: #bee3f8; + } + + .xl\:focus\:to-blue-300:focus { + --gradient-to-color: #90cdf4; + } + + .xl\:focus\:to-blue-400:focus { + --gradient-to-color: #63b3ed; + } + + .xl\:focus\:to-blue-500:focus { + --gradient-to-color: #4299e1; + } + + .xl\:focus\:to-blue-600:focus { + --gradient-to-color: #3182ce; + } + + .xl\:focus\:to-blue-700:focus { + --gradient-to-color: #2b6cb0; + } + + .xl\:focus\:to-blue-800:focus { + --gradient-to-color: #2c5282; + } + + .xl\:focus\:to-blue-900:focus { + --gradient-to-color: #2a4365; + } + + .xl\:focus\:to-indigo-100:focus { + --gradient-to-color: #ebf4ff; + } + + .xl\:focus\:to-indigo-200:focus { + --gradient-to-color: #c3dafe; + } + + .xl\:focus\:to-indigo-300:focus { + --gradient-to-color: #a3bffa; + } + + .xl\:focus\:to-indigo-400:focus { + --gradient-to-color: #7f9cf5; + } + + .xl\:focus\:to-indigo-500:focus { + --gradient-to-color: #667eea; + } + + .xl\:focus\:to-indigo-600:focus { + --gradient-to-color: #5a67d8; + } + + .xl\:focus\:to-indigo-700:focus { + --gradient-to-color: #4c51bf; + } + + .xl\:focus\:to-indigo-800:focus { + --gradient-to-color: #434190; + } + + .xl\:focus\:to-indigo-900:focus { + --gradient-to-color: #3c366b; + } + + .xl\:focus\:to-purple-100:focus { + --gradient-to-color: #faf5ff; + } + + .xl\:focus\:to-purple-200:focus { + --gradient-to-color: #e9d8fd; + } + + .xl\:focus\:to-purple-300:focus { + --gradient-to-color: #d6bcfa; + } + + .xl\:focus\:to-purple-400:focus { + --gradient-to-color: #b794f4; + } + + .xl\:focus\:to-purple-500:focus { + --gradient-to-color: #9f7aea; + } + + .xl\:focus\:to-purple-600:focus { + --gradient-to-color: #805ad5; + } + + .xl\:focus\:to-purple-700:focus { + --gradient-to-color: #6b46c1; + } + + .xl\:focus\:to-purple-800:focus { + --gradient-to-color: #553c9a; + } + + .xl\:focus\:to-purple-900:focus { + --gradient-to-color: #44337a; + } + + .xl\:focus\:to-pink-100:focus { + --gradient-to-color: #fff5f7; + } + + .xl\:focus\:to-pink-200:focus { + --gradient-to-color: #fed7e2; + } + + .xl\:focus\:to-pink-300:focus { + --gradient-to-color: #fbb6ce; + } + + .xl\:focus\:to-pink-400:focus { + --gradient-to-color: #f687b3; + } + + .xl\:focus\:to-pink-500:focus { + --gradient-to-color: #ed64a6; + } + + .xl\:focus\:to-pink-600:focus { + --gradient-to-color: #d53f8c; + } + + .xl\:focus\:to-pink-700:focus { + --gradient-to-color: #b83280; + } + + .xl\:focus\:to-pink-800:focus { + --gradient-to-color: #97266d; + } + + .xl\:focus\:to-pink-900:focus { + --gradient-to-color: #702459; + } + + .xl\:bg-opacity-0 { + --bg-opacity: 0; + } + + .xl\:bg-opacity-25 { + --bg-opacity: 0.25; + } + + .xl\:bg-opacity-50 { + --bg-opacity: 0.5; + } + + .xl\:bg-opacity-75 { + --bg-opacity: 0.75; + } + + .xl\:bg-opacity-100 { + --bg-opacity: 1; + } + + .xl\:hover\:bg-opacity-0:hover { + --bg-opacity: 0; + } + + .xl\:hover\:bg-opacity-25:hover { + --bg-opacity: 0.25; + } + + .xl\:hover\:bg-opacity-50:hover { + --bg-opacity: 0.5; + } + + .xl\:hover\:bg-opacity-75:hover { + --bg-opacity: 0.75; + } + + .xl\:hover\:bg-opacity-100:hover { + --bg-opacity: 1; + } + + .xl\:focus\:bg-opacity-0:focus { + --bg-opacity: 0; + } + + .xl\:focus\:bg-opacity-25:focus { + --bg-opacity: 0.25; + } + + .xl\:focus\:bg-opacity-50:focus { + --bg-opacity: 0.5; + } + + .xl\:focus\:bg-opacity-75:focus { + --bg-opacity: 0.75; + } + + .xl\:focus\:bg-opacity-100:focus { + --bg-opacity: 1; + } + + .xl\:bg-bottom { + background-position: bottom; + } + + .xl\:bg-center { + background-position: center; + } + + .xl\:bg-left { + background-position: left; + } + + .xl\:bg-left-bottom { + background-position: left bottom; + } + + .xl\:bg-left-top { + background-position: left top; + } + + .xl\:bg-right { + background-position: right; + } + + .xl\:bg-right-bottom { + background-position: right bottom; + } + + .xl\:bg-right-top { + background-position: right top; + } + + .xl\:bg-top { + background-position: top; + } + + .xl\:bg-repeat { + background-repeat: repeat; + } + + .xl\:bg-no-repeat { + background-repeat: no-repeat; + } + + .xl\:bg-repeat-x { + background-repeat: repeat-x; + } + + .xl\:bg-repeat-y { + background-repeat: repeat-y; + } + + .xl\:bg-repeat-round { + background-repeat: round; + } + + .xl\:bg-repeat-space { + background-repeat: space; + } + + .xl\:bg-auto { + background-size: auto; + } + + .xl\:bg-cover { + background-size: cover; + } + + .xl\:bg-contain { + background-size: contain; + } + + .xl\:border-collapse { + border-collapse: collapse; + } + + .xl\:border-separate { + border-collapse: separate; + } + + .xl\:border-transparent { + border-color: transparent; + } + + .xl\:border-current { + border-color: currentColor; + } + + .xl\:border-black { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .xl\:border-white { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .xl\:border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .xl\:border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .xl\:border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .xl\:border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .xl\:border-gray-500 { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .xl\:border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .xl\:border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .xl\:border-gray-800 { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .xl\:border-gray-900 { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .xl\:border-red-100 { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .xl\:border-red-200 { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .xl\:border-red-300 { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .xl\:border-red-400 { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .xl\:border-red-500 { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .xl\:border-red-600 { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .xl\:border-red-700 { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .xl\:border-red-800 { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .xl\:border-red-900 { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .xl\:border-orange-100 { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .xl\:border-orange-200 { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .xl\:border-orange-300 { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .xl\:border-orange-400 { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .xl\:border-orange-500 { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .xl\:border-orange-600 { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .xl\:border-orange-700 { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .xl\:border-orange-800 { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .xl\:border-orange-900 { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .xl\:border-yellow-100 { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .xl\:border-yellow-200 { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .xl\:border-yellow-300 { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .xl\:border-yellow-400 { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .xl\:border-yellow-500 { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .xl\:border-yellow-600 { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .xl\:border-yellow-700 { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .xl\:border-yellow-800 { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .xl\:border-yellow-900 { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .xl\:border-green-100 { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .xl\:border-green-200 { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .xl\:border-green-300 { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .xl\:border-green-400 { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .xl\:border-green-500 { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .xl\:border-green-600 { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .xl\:border-green-700 { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .xl\:border-green-800 { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .xl\:border-green-900 { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .xl\:border-teal-100 { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .xl\:border-teal-200 { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .xl\:border-teal-300 { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .xl\:border-teal-400 { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .xl\:border-teal-500 { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .xl\:border-teal-600 { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .xl\:border-teal-700 { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .xl\:border-teal-800 { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .xl\:border-teal-900 { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .xl\:border-blue-100 { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .xl\:border-blue-200 { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .xl\:border-blue-300 { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .xl\:border-blue-400 { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .xl\:border-blue-500 { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .xl\:border-blue-600 { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .xl\:border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .xl\:border-blue-800 { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .xl\:border-blue-900 { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .xl\:border-indigo-100 { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .xl\:border-indigo-200 { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .xl\:border-indigo-300 { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .xl\:border-indigo-400 { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .xl\:border-indigo-500 { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .xl\:border-indigo-600 { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .xl\:border-indigo-700 { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .xl\:border-indigo-800 { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .xl\:border-indigo-900 { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .xl\:border-purple-100 { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .xl\:border-purple-200 { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .xl\:border-purple-300 { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .xl\:border-purple-400 { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .xl\:border-purple-500 { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .xl\:border-purple-600 { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .xl\:border-purple-700 { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .xl\:border-purple-800 { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .xl\:border-purple-900 { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .xl\:border-pink-100 { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .xl\:border-pink-200 { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .xl\:border-pink-300 { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .xl\:border-pink-400 { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .xl\:border-pink-500 { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .xl\:border-pink-600 { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .xl\:border-pink-700 { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .xl\:border-pink-800 { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .xl\:border-pink-900 { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .xl\:hover\:border-transparent:hover { + border-color: transparent; + } + + .xl\:hover\:border-current:hover { + border-color: currentColor; + } + + .xl\:hover\:border-black:hover { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .xl\:hover\:border-white:hover { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .xl\:hover\:border-gray-100:hover { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .xl\:hover\:border-gray-200:hover { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .xl\:hover\:border-gray-300:hover { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .xl\:hover\:border-gray-400:hover { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .xl\:hover\:border-gray-500:hover { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .xl\:hover\:border-gray-600:hover { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .xl\:hover\:border-gray-700:hover { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .xl\:hover\:border-gray-800:hover { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .xl\:hover\:border-gray-900:hover { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .xl\:hover\:border-red-100:hover { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .xl\:hover\:border-red-200:hover { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .xl\:hover\:border-red-300:hover { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .xl\:hover\:border-red-400:hover { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .xl\:hover\:border-red-500:hover { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .xl\:hover\:border-red-600:hover { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .xl\:hover\:border-red-700:hover { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .xl\:hover\:border-red-800:hover { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .xl\:hover\:border-red-900:hover { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .xl\:hover\:border-orange-100:hover { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .xl\:hover\:border-orange-200:hover { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .xl\:hover\:border-orange-300:hover { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .xl\:hover\:border-orange-400:hover { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .xl\:hover\:border-orange-500:hover { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .xl\:hover\:border-orange-600:hover { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .xl\:hover\:border-orange-700:hover { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .xl\:hover\:border-orange-800:hover { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .xl\:hover\:border-orange-900:hover { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-100:hover { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-200:hover { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-300:hover { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-400:hover { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-500:hover { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-600:hover { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-700:hover { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-800:hover { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .xl\:hover\:border-yellow-900:hover { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .xl\:hover\:border-green-100:hover { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .xl\:hover\:border-green-200:hover { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .xl\:hover\:border-green-300:hover { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .xl\:hover\:border-green-400:hover { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .xl\:hover\:border-green-500:hover { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .xl\:hover\:border-green-600:hover { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .xl\:hover\:border-green-700:hover { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .xl\:hover\:border-green-800:hover { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .xl\:hover\:border-green-900:hover { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .xl\:hover\:border-teal-100:hover { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .xl\:hover\:border-teal-200:hover { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .xl\:hover\:border-teal-300:hover { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .xl\:hover\:border-teal-400:hover { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .xl\:hover\:border-teal-500:hover { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .xl\:hover\:border-teal-600:hover { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .xl\:hover\:border-teal-700:hover { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .xl\:hover\:border-teal-800:hover { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .xl\:hover\:border-teal-900:hover { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .xl\:hover\:border-blue-100:hover { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .xl\:hover\:border-blue-200:hover { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .xl\:hover\:border-blue-300:hover { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .xl\:hover\:border-blue-400:hover { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .xl\:hover\:border-blue-500:hover { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .xl\:hover\:border-blue-600:hover { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .xl\:hover\:border-blue-700:hover { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .xl\:hover\:border-blue-800:hover { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .xl\:hover\:border-blue-900:hover { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-100:hover { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-200:hover { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-300:hover { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-400:hover { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-500:hover { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-600:hover { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-700:hover { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-800:hover { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .xl\:hover\:border-indigo-900:hover { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .xl\:hover\:border-purple-100:hover { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .xl\:hover\:border-purple-200:hover { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .xl\:hover\:border-purple-300:hover { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .xl\:hover\:border-purple-400:hover { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .xl\:hover\:border-purple-500:hover { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .xl\:hover\:border-purple-600:hover { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .xl\:hover\:border-purple-700:hover { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .xl\:hover\:border-purple-800:hover { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .xl\:hover\:border-purple-900:hover { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .xl\:hover\:border-pink-100:hover { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .xl\:hover\:border-pink-200:hover { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .xl\:hover\:border-pink-300:hover { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .xl\:hover\:border-pink-400:hover { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .xl\:hover\:border-pink-500:hover { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .xl\:hover\:border-pink-600:hover { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .xl\:hover\:border-pink-700:hover { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .xl\:hover\:border-pink-800:hover { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .xl\:hover\:border-pink-900:hover { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .xl\:focus\:border-transparent:focus { + border-color: transparent; + } + + .xl\:focus\:border-current:focus { + border-color: currentColor; + } + + .xl\:focus\:border-black:focus { + --border-opacity: 1; + border-color: #000; + border-color: rgba(0, 0, 0, var(--border-opacity)); + } + + .xl\:focus\:border-white:focus { + --border-opacity: 1; + border-color: #fff; + border-color: rgba(255, 255, 255, var(--border-opacity)); + } + + .xl\:focus\:border-gray-100:focus { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); + } + + .xl\:focus\:border-gray-200:focus { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); + } + + .xl\:focus\:border-gray-300:focus { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); + } + + .xl\:focus\:border-gray-400:focus { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); + } + + .xl\:focus\:border-gray-500:focus { + --border-opacity: 1; + border-color: #a0aec0; + border-color: rgba(160, 174, 192, var(--border-opacity)); + } + + .xl\:focus\:border-gray-600:focus { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); + } + + .xl\:focus\:border-gray-700:focus { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); + } + + .xl\:focus\:border-gray-800:focus { + --border-opacity: 1; + border-color: #2d3748; + border-color: rgba(45, 55, 72, var(--border-opacity)); + } + + .xl\:focus\:border-gray-900:focus { + --border-opacity: 1; + border-color: #1a202c; + border-color: rgba(26, 32, 44, var(--border-opacity)); + } + + .xl\:focus\:border-red-100:focus { + --border-opacity: 1; + border-color: #fff5f5; + border-color: rgba(255, 245, 245, var(--border-opacity)); + } + + .xl\:focus\:border-red-200:focus { + --border-opacity: 1; + border-color: #fed7d7; + border-color: rgba(254, 215, 215, var(--border-opacity)); + } + + .xl\:focus\:border-red-300:focus { + --border-opacity: 1; + border-color: #feb2b2; + border-color: rgba(254, 178, 178, var(--border-opacity)); + } + + .xl\:focus\:border-red-400:focus { + --border-opacity: 1; + border-color: #fc8181; + border-color: rgba(252, 129, 129, var(--border-opacity)); + } + + .xl\:focus\:border-red-500:focus { + --border-opacity: 1; + border-color: #f56565; + border-color: rgba(245, 101, 101, var(--border-opacity)); + } + + .xl\:focus\:border-red-600:focus { + --border-opacity: 1; + border-color: #e53e3e; + border-color: rgba(229, 62, 62, var(--border-opacity)); + } + + .xl\:focus\:border-red-700:focus { + --border-opacity: 1; + border-color: #c53030; + border-color: rgba(197, 48, 48, var(--border-opacity)); + } + + .xl\:focus\:border-red-800:focus { + --border-opacity: 1; + border-color: #9b2c2c; + border-color: rgba(155, 44, 44, var(--border-opacity)); + } + + .xl\:focus\:border-red-900:focus { + --border-opacity: 1; + border-color: #742a2a; + border-color: rgba(116, 42, 42, var(--border-opacity)); + } + + .xl\:focus\:border-orange-100:focus { + --border-opacity: 1; + border-color: #fffaf0; + border-color: rgba(255, 250, 240, var(--border-opacity)); + } + + .xl\:focus\:border-orange-200:focus { + --border-opacity: 1; + border-color: #feebc8; + border-color: rgba(254, 235, 200, var(--border-opacity)); + } + + .xl\:focus\:border-orange-300:focus { + --border-opacity: 1; + border-color: #fbd38d; + border-color: rgba(251, 211, 141, var(--border-opacity)); + } + + .xl\:focus\:border-orange-400:focus { + --border-opacity: 1; + border-color: #f6ad55; + border-color: rgba(246, 173, 85, var(--border-opacity)); + } + + .xl\:focus\:border-orange-500:focus { + --border-opacity: 1; + border-color: #ed8936; + border-color: rgba(237, 137, 54, var(--border-opacity)); + } + + .xl\:focus\:border-orange-600:focus { + --border-opacity: 1; + border-color: #dd6b20; + border-color: rgba(221, 107, 32, var(--border-opacity)); + } + + .xl\:focus\:border-orange-700:focus { + --border-opacity: 1; + border-color: #c05621; + border-color: rgba(192, 86, 33, var(--border-opacity)); + } + + .xl\:focus\:border-orange-800:focus { + --border-opacity: 1; + border-color: #9c4221; + border-color: rgba(156, 66, 33, var(--border-opacity)); + } + + .xl\:focus\:border-orange-900:focus { + --border-opacity: 1; + border-color: #7b341e; + border-color: rgba(123, 52, 30, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-100:focus { + --border-opacity: 1; + border-color: #fffff0; + border-color: rgba(255, 255, 240, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-200:focus { + --border-opacity: 1; + border-color: #fefcbf; + border-color: rgba(254, 252, 191, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-300:focus { + --border-opacity: 1; + border-color: #faf089; + border-color: rgba(250, 240, 137, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-400:focus { + --border-opacity: 1; + border-color: #f6e05e; + border-color: rgba(246, 224, 94, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-500:focus { + --border-opacity: 1; + border-color: #ecc94b; + border-color: rgba(236, 201, 75, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-600:focus { + --border-opacity: 1; + border-color: #d69e2e; + border-color: rgba(214, 158, 46, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-700:focus { + --border-opacity: 1; + border-color: #b7791f; + border-color: rgba(183, 121, 31, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-800:focus { + --border-opacity: 1; + border-color: #975a16; + border-color: rgba(151, 90, 22, var(--border-opacity)); + } + + .xl\:focus\:border-yellow-900:focus { + --border-opacity: 1; + border-color: #744210; + border-color: rgba(116, 66, 16, var(--border-opacity)); + } + + .xl\:focus\:border-green-100:focus { + --border-opacity: 1; + border-color: #f0fff4; + border-color: rgba(240, 255, 244, var(--border-opacity)); + } + + .xl\:focus\:border-green-200:focus { + --border-opacity: 1; + border-color: #c6f6d5; + border-color: rgba(198, 246, 213, var(--border-opacity)); + } + + .xl\:focus\:border-green-300:focus { + --border-opacity: 1; + border-color: #9ae6b4; + border-color: rgba(154, 230, 180, var(--border-opacity)); + } + + .xl\:focus\:border-green-400:focus { + --border-opacity: 1; + border-color: #68d391; + border-color: rgba(104, 211, 145, var(--border-opacity)); + } + + .xl\:focus\:border-green-500:focus { + --border-opacity: 1; + border-color: #48bb78; + border-color: rgba(72, 187, 120, var(--border-opacity)); + } + + .xl\:focus\:border-green-600:focus { + --border-opacity: 1; + border-color: #38a169; + border-color: rgba(56, 161, 105, var(--border-opacity)); + } + + .xl\:focus\:border-green-700:focus { + --border-opacity: 1; + border-color: #2f855a; + border-color: rgba(47, 133, 90, var(--border-opacity)); + } + + .xl\:focus\:border-green-800:focus { + --border-opacity: 1; + border-color: #276749; + border-color: rgba(39, 103, 73, var(--border-opacity)); + } + + .xl\:focus\:border-green-900:focus { + --border-opacity: 1; + border-color: #22543d; + border-color: rgba(34, 84, 61, var(--border-opacity)); + } + + .xl\:focus\:border-teal-100:focus { + --border-opacity: 1; + border-color: #e6fffa; + border-color: rgba(230, 255, 250, var(--border-opacity)); + } + + .xl\:focus\:border-teal-200:focus { + --border-opacity: 1; + border-color: #b2f5ea; + border-color: rgba(178, 245, 234, var(--border-opacity)); + } + + .xl\:focus\:border-teal-300:focus { + --border-opacity: 1; + border-color: #81e6d9; + border-color: rgba(129, 230, 217, var(--border-opacity)); + } + + .xl\:focus\:border-teal-400:focus { + --border-opacity: 1; + border-color: #4fd1c5; + border-color: rgba(79, 209, 197, var(--border-opacity)); + } + + .xl\:focus\:border-teal-500:focus { + --border-opacity: 1; + border-color: #38b2ac; + border-color: rgba(56, 178, 172, var(--border-opacity)); + } + + .xl\:focus\:border-teal-600:focus { + --border-opacity: 1; + border-color: #319795; + border-color: rgba(49, 151, 149, var(--border-opacity)); + } + + .xl\:focus\:border-teal-700:focus { + --border-opacity: 1; + border-color: #2c7a7b; + border-color: rgba(44, 122, 123, var(--border-opacity)); + } + + .xl\:focus\:border-teal-800:focus { + --border-opacity: 1; + border-color: #285e61; + border-color: rgba(40, 94, 97, var(--border-opacity)); + } + + .xl\:focus\:border-teal-900:focus { + --border-opacity: 1; + border-color: #234e52; + border-color: rgba(35, 78, 82, var(--border-opacity)); + } + + .xl\:focus\:border-blue-100:focus { + --border-opacity: 1; + border-color: #ebf8ff; + border-color: rgba(235, 248, 255, var(--border-opacity)); + } + + .xl\:focus\:border-blue-200:focus { + --border-opacity: 1; + border-color: #bee3f8; + border-color: rgba(190, 227, 248, var(--border-opacity)); + } + + .xl\:focus\:border-blue-300:focus { + --border-opacity: 1; + border-color: #90cdf4; + border-color: rgba(144, 205, 244, var(--border-opacity)); + } + + .xl\:focus\:border-blue-400:focus { + --border-opacity: 1; + border-color: #63b3ed; + border-color: rgba(99, 179, 237, var(--border-opacity)); + } + + .xl\:focus\:border-blue-500:focus { + --border-opacity: 1; + border-color: #4299e1; + border-color: rgba(66, 153, 225, var(--border-opacity)); + } + + .xl\:focus\:border-blue-600:focus { + --border-opacity: 1; + border-color: #3182ce; + border-color: rgba(49, 130, 206, var(--border-opacity)); + } + + .xl\:focus\:border-blue-700:focus { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); + } + + .xl\:focus\:border-blue-800:focus { + --border-opacity: 1; + border-color: #2c5282; + border-color: rgba(44, 82, 130, var(--border-opacity)); + } + + .xl\:focus\:border-blue-900:focus { + --border-opacity: 1; + border-color: #2a4365; + border-color: rgba(42, 67, 101, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-100:focus { + --border-opacity: 1; + border-color: #ebf4ff; + border-color: rgba(235, 244, 255, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-200:focus { + --border-opacity: 1; + border-color: #c3dafe; + border-color: rgba(195, 218, 254, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-300:focus { + --border-opacity: 1; + border-color: #a3bffa; + border-color: rgba(163, 191, 250, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-400:focus { + --border-opacity: 1; + border-color: #7f9cf5; + border-color: rgba(127, 156, 245, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-500:focus { + --border-opacity: 1; + border-color: #667eea; + border-color: rgba(102, 126, 234, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-600:focus { + --border-opacity: 1; + border-color: #5a67d8; + border-color: rgba(90, 103, 216, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-700:focus { + --border-opacity: 1; + border-color: #4c51bf; + border-color: rgba(76, 81, 191, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-800:focus { + --border-opacity: 1; + border-color: #434190; + border-color: rgba(67, 65, 144, var(--border-opacity)); + } + + .xl\:focus\:border-indigo-900:focus { + --border-opacity: 1; + border-color: #3c366b; + border-color: rgba(60, 54, 107, var(--border-opacity)); + } + + .xl\:focus\:border-purple-100:focus { + --border-opacity: 1; + border-color: #faf5ff; + border-color: rgba(250, 245, 255, var(--border-opacity)); + } + + .xl\:focus\:border-purple-200:focus { + --border-opacity: 1; + border-color: #e9d8fd; + border-color: rgba(233, 216, 253, var(--border-opacity)); + } + + .xl\:focus\:border-purple-300:focus { + --border-opacity: 1; + border-color: #d6bcfa; + border-color: rgba(214, 188, 250, var(--border-opacity)); + } + + .xl\:focus\:border-purple-400:focus { + --border-opacity: 1; + border-color: #b794f4; + border-color: rgba(183, 148, 244, var(--border-opacity)); + } + + .xl\:focus\:border-purple-500:focus { + --border-opacity: 1; + border-color: #9f7aea; + border-color: rgba(159, 122, 234, var(--border-opacity)); + } + + .xl\:focus\:border-purple-600:focus { + --border-opacity: 1; + border-color: #805ad5; + border-color: rgba(128, 90, 213, var(--border-opacity)); + } + + .xl\:focus\:border-purple-700:focus { + --border-opacity: 1; + border-color: #6b46c1; + border-color: rgba(107, 70, 193, var(--border-opacity)); + } + + .xl\:focus\:border-purple-800:focus { + --border-opacity: 1; + border-color: #553c9a; + border-color: rgba(85, 60, 154, var(--border-opacity)); + } + + .xl\:focus\:border-purple-900:focus { + --border-opacity: 1; + border-color: #44337a; + border-color: rgba(68, 51, 122, var(--border-opacity)); + } + + .xl\:focus\:border-pink-100:focus { + --border-opacity: 1; + border-color: #fff5f7; + border-color: rgba(255, 245, 247, var(--border-opacity)); + } + + .xl\:focus\:border-pink-200:focus { + --border-opacity: 1; + border-color: #fed7e2; + border-color: rgba(254, 215, 226, var(--border-opacity)); + } + + .xl\:focus\:border-pink-300:focus { + --border-opacity: 1; + border-color: #fbb6ce; + border-color: rgba(251, 182, 206, var(--border-opacity)); + } + + .xl\:focus\:border-pink-400:focus { + --border-opacity: 1; + border-color: #f687b3; + border-color: rgba(246, 135, 179, var(--border-opacity)); + } + + .xl\:focus\:border-pink-500:focus { + --border-opacity: 1; + border-color: #ed64a6; + border-color: rgba(237, 100, 166, var(--border-opacity)); + } + + .xl\:focus\:border-pink-600:focus { + --border-opacity: 1; + border-color: #d53f8c; + border-color: rgba(213, 63, 140, var(--border-opacity)); + } + + .xl\:focus\:border-pink-700:focus { + --border-opacity: 1; + border-color: #b83280; + border-color: rgba(184, 50, 128, var(--border-opacity)); + } + + .xl\:focus\:border-pink-800:focus { + --border-opacity: 1; + border-color: #97266d; + border-color: rgba(151, 38, 109, var(--border-opacity)); + } + + .xl\:focus\:border-pink-900:focus { + --border-opacity: 1; + border-color: #702459; + border-color: rgba(112, 36, 89, var(--border-opacity)); + } + + .xl\:border-opacity-0 { + --border-opacity: 0; + } + + .xl\:border-opacity-25 { + --border-opacity: 0.25; + } + + .xl\:border-opacity-50 { + --border-opacity: 0.5; + } + + .xl\:border-opacity-75 { + --border-opacity: 0.75; + } + + .xl\:border-opacity-100 { + --border-opacity: 1; + } + + .xl\:hover\:border-opacity-0:hover { + --border-opacity: 0; + } + + .xl\:hover\:border-opacity-25:hover { + --border-opacity: 0.25; + } + + .xl\:hover\:border-opacity-50:hover { + --border-opacity: 0.5; + } + + .xl\:hover\:border-opacity-75:hover { + --border-opacity: 0.75; + } + + .xl\:hover\:border-opacity-100:hover { + --border-opacity: 1; + } + + .xl\:focus\:border-opacity-0:focus { + --border-opacity: 0; + } + + .xl\:focus\:border-opacity-25:focus { + --border-opacity: 0.25; + } + + .xl\:focus\:border-opacity-50:focus { + --border-opacity: 0.5; + } + + .xl\:focus\:border-opacity-75:focus { + --border-opacity: 0.75; + } + + .xl\:focus\:border-opacity-100:focus { + --border-opacity: 1; + } + + .xl\:rounded-none { + border-radius: 0; + } + + .xl\:rounded-sm { + border-radius: 0.125rem; + } + + .xl\:rounded { + border-radius: 0.25rem; + } + + .xl\:rounded-md { + border-radius: 0.375rem; + } + + .xl\:rounded-lg { + border-radius: 0.5rem; + } + + .xl\:rounded-full { + border-radius: 9999px; + } + + .xl\:rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .xl\:rounded-r-none { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .xl\:rounded-b-none { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + + .xl\:rounded-l-none { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .xl\:rounded-t-sm { + border-top-left-radius: 0.125rem; + border-top-right-radius: 0.125rem; + } + + .xl\:rounded-r-sm { + border-top-right-radius: 0.125rem; + border-bottom-right-radius: 0.125rem; + } + + .xl\:rounded-b-sm { + border-bottom-right-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .xl\:rounded-l-sm { + border-top-left-radius: 0.125rem; + border-bottom-left-radius: 0.125rem; + } + + .xl\:rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + + .xl\:rounded-r { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + } + + .xl\:rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .xl\:rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + + .xl\:rounded-t-md { + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; + } + + .xl\:rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + } + + .xl\:rounded-b-md { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .xl\:rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; + } + + .xl\:rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } + + .xl\:rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + } + + .xl\:rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .xl\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .xl\:rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px; + } + + .xl\:rounded-r-full { + border-top-right-radius: 9999px; + border-bottom-right-radius: 9999px; + } + + .xl\:rounded-b-full { + border-bottom-right-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .xl\:rounded-l-full { + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; + } + + .xl\:rounded-tl-none { + border-top-left-radius: 0; + } + + .xl\:rounded-tr-none { + border-top-right-radius: 0; + } + + .xl\:rounded-br-none { + border-bottom-right-radius: 0; + } + + .xl\:rounded-bl-none { + border-bottom-left-radius: 0; + } + + .xl\:rounded-tl-sm { + border-top-left-radius: 0.125rem; + } + + .xl\:rounded-tr-sm { + border-top-right-radius: 0.125rem; + } + + .xl\:rounded-br-sm { + border-bottom-right-radius: 0.125rem; + } + + .xl\:rounded-bl-sm { + border-bottom-left-radius: 0.125rem; + } + + .xl\:rounded-tl { + border-top-left-radius: 0.25rem; + } + + .xl\:rounded-tr { + border-top-right-radius: 0.25rem; + } + + .xl\:rounded-br { + border-bottom-right-radius: 0.25rem; + } + + .xl\:rounded-bl { + border-bottom-left-radius: 0.25rem; + } + + .xl\:rounded-tl-md { + border-top-left-radius: 0.375rem; + } + + .xl\:rounded-tr-md { + border-top-right-radius: 0.375rem; + } + + .xl\:rounded-br-md { + border-bottom-right-radius: 0.375rem; + } + + .xl\:rounded-bl-md { + border-bottom-left-radius: 0.375rem; + } + + .xl\:rounded-tl-lg { + border-top-left-radius: 0.5rem; + } + + .xl\:rounded-tr-lg { + border-top-right-radius: 0.5rem; + } + + .xl\:rounded-br-lg { + border-bottom-right-radius: 0.5rem; + } + + .xl\:rounded-bl-lg { + border-bottom-left-radius: 0.5rem; + } + + .xl\:rounded-tl-full { + border-top-left-radius: 9999px; + } + + .xl\:rounded-tr-full { + border-top-right-radius: 9999px; + } + + .xl\:rounded-br-full { + border-bottom-right-radius: 9999px; + } + + .xl\:rounded-bl-full { + border-bottom-left-radius: 9999px; + } + + .xl\:border-solid { + border-style: solid; + } + + .xl\:border-dashed { + border-style: dashed; + } + + .xl\:border-dotted { + border-style: dotted; + } + + .xl\:border-double { + border-style: double; + } + + .xl\:border-none { + border-style: none; + } + + .xl\:border-0 { + border-width: 0; + } + + .xl\:border-2 { + border-width: 2px; + } + + .xl\:border-4 { + border-width: 4px; + } + + .xl\:border-8 { + border-width: 8px; + } + + .xl\:border { + border-width: 1px; + } + + .xl\:border-t-0 { + border-top-width: 0; + } + + .xl\:border-r-0 { + border-right-width: 0; + } + + .xl\:border-b-0 { + border-bottom-width: 0; + } + + .xl\:border-l-0 { + border-left-width: 0; + } + + .xl\:border-t-2 { + border-top-width: 2px; + } + + .xl\:border-r-2 { + border-right-width: 2px; + } + + .xl\:border-b-2 { + border-bottom-width: 2px; + } + + .xl\:border-l-2 { + border-left-width: 2px; + } + + .xl\:border-t-4 { + border-top-width: 4px; + } + + .xl\:border-r-4 { + border-right-width: 4px; + } + + .xl\:border-b-4 { + border-bottom-width: 4px; + } + + .xl\:border-l-4 { + border-left-width: 4px; + } + + .xl\:border-t-8 { + border-top-width: 8px; + } + + .xl\:border-r-8 { + border-right-width: 8px; + } + + .xl\:border-b-8 { + border-bottom-width: 8px; + } + + .xl\:border-l-8 { + border-left-width: 8px; + } + + .xl\:border-t { + border-top-width: 1px; + } + + .xl\:border-r { + border-right-width: 1px; + } + + .xl\:border-b { + border-bottom-width: 1px; + } + + .xl\:border-l { + border-left-width: 1px; + } + + .xl\:box-border { + box-sizing: border-box; + } + + .xl\:box-content { + box-sizing: content-box; + } + + .xl\:cursor-auto { + cursor: auto; + } + + .xl\:cursor-default { + cursor: default; + } + + .xl\:cursor-pointer { + cursor: pointer; + } + + .xl\:cursor-wait { + cursor: wait; + } + + .xl\:cursor-text { + cursor: text; + } + + .xl\:cursor-move { + cursor: move; + } + + .xl\:cursor-not-allowed { + cursor: not-allowed; + } + + .xl\:block { + display: block; + } + + .xl\:inline-block { + display: inline-block; + } + + .xl\:inline { + display: inline; + } + + .xl\:flex { + display: flex; + } + + .xl\:inline-flex { + display: inline-flex; + } + + .xl\:table { + display: table; + } + + .xl\:table-caption { + display: table-caption; + } + + .xl\:table-cell { + display: table-cell; + } + + .xl\:table-column { + display: table-column; + } + + .xl\:table-column-group { + display: table-column-group; + } + + .xl\:table-footer-group { + display: table-footer-group; + } + + .xl\:table-header-group { + display: table-header-group; + } + + .xl\:table-row-group { + display: table-row-group; + } + + .xl\:table-row { + display: table-row; + } + + .xl\:flow-root { + display: flow-root; + } + + .xl\:grid { + display: grid; + } + + .xl\:inline-grid { + display: inline-grid; + } + + .xl\:contents { + display: contents; + } + + .xl\:hidden { + display: none; + } + + .xl\:flex-row { + flex-direction: row; + } + + .xl\:flex-row-reverse { + flex-direction: row-reverse; + } + + .xl\:flex-col { + flex-direction: column; + } + + .xl\:flex-col-reverse { + flex-direction: column-reverse; + } + + .xl\:flex-wrap { + flex-wrap: wrap; + } + + .xl\:flex-wrap-reverse { + flex-wrap: wrap-reverse; + } + + .xl\:flex-no-wrap { + flex-wrap: nowrap; + } + + .xl\:place-items-auto { + place-items: auto; + } + + .xl\:place-items-start { + place-items: start; + } + + .xl\:place-items-end { + place-items: end; + } + + .xl\:place-items-center { + place-items: center; + } + + .xl\:place-items-stretch { + place-items: stretch; + } + + .xl\:place-content-center { + place-content: center; + } + + .xl\:place-content-start { + place-content: start; + } + + .xl\:place-content-end { + place-content: end; + } + + .xl\:place-content-between { + place-content: space-between; + } + + .xl\:place-content-around { + place-content: space-around; + } + + .xl\:place-content-evenly { + place-content: space-evenly; + } + + .xl\:place-content-stretch { + place-content: stretch; + } + + .xl\:place-self-auto { + place-self: auto; + } + + .xl\:place-self-start { + place-self: start; + } + + .xl\:place-self-end { + place-self: end; + } + + .xl\:place-self-center { + place-self: center; + } + + .xl\:place-self-stretch { + place-self: stretch; + } + + .xl\:items-start { + align-items: flex-start; + } + + .xl\:items-end { + align-items: flex-end; + } + + .xl\:items-center { + align-items: center; + } + + .xl\:items-baseline { + align-items: baseline; + } + + .xl\:items-stretch { + align-items: stretch; + } + + .xl\:content-center { + align-content: center; + } + + .xl\:content-start { + align-content: flex-start; + } + + .xl\:content-end { + align-content: flex-end; + } + + .xl\:content-between { + align-content: space-between; + } + + .xl\:content-around { + align-content: space-around; + } + + .xl\:content-evenly { + align-content: space-evenly; + } + + .xl\:self-auto { + align-self: auto; + } + + .xl\:self-start { + align-self: flex-start; + } + + .xl\:self-end { + align-self: flex-end; + } + + .xl\:self-center { + align-self: center; + } + + .xl\:self-stretch { + align-self: stretch; + } + + .xl\:justify-items-auto { + justify-items: auto; + } + + .xl\:justify-items-start { + justify-items: start; + } + + .xl\:justify-items-end { + justify-items: end; + } + + .xl\:justify-items-center { + justify-items: center; + } + + .xl\:justify-items-stretch { + justify-items: stretch; + } + + .xl\:justify-start { + justify-content: flex-start; + } + + .xl\:justify-end { + justify-content: flex-end; + } + + .xl\:justify-center { + justify-content: center; + } + + .xl\:justify-between { + justify-content: space-between; + } + + .xl\:justify-around { + justify-content: space-around; + } + + .xl\:justify-evenly { + justify-content: space-evenly; + } + + .xl\:justify-self-auto { + justify-self: auto; + } + + .xl\:justify-self-start { + justify-self: start; + } + + .xl\:justify-self-end { + justify-self: end; + } + + .xl\:justify-self-center { + justify-self: center; + } + + .xl\:justify-self-stretch { + justify-self: stretch; + } + + .xl\:flex-1 { + flex: 1 1 0%; + } + + .xl\:flex-auto { + flex: 1 1 auto; + } + + .xl\:flex-initial { + flex: 0 1 auto; + } + + .xl\:flex-none { + flex: none; + } + + .xl\:flex-grow-0 { + flex-grow: 0; + } + + .xl\:flex-grow { + flex-grow: 1; + } + + .xl\:flex-shrink-0 { + flex-shrink: 0; + } + + .xl\:flex-shrink { + flex-shrink: 1; + } + + .xl\:order-1 { + order: 1; + } + + .xl\:order-2 { + order: 2; + } + + .xl\:order-3 { + order: 3; + } + + .xl\:order-4 { + order: 4; + } + + .xl\:order-5 { + order: 5; + } + + .xl\:order-6 { + order: 6; + } + + .xl\:order-7 { + order: 7; + } + + .xl\:order-8 { + order: 8; + } + + .xl\:order-9 { + order: 9; + } + + .xl\:order-10 { + order: 10; + } + + .xl\:order-11 { + order: 11; + } + + .xl\:order-12 { + order: 12; + } + + .xl\:order-first { + order: -9999; + } + + .xl\:order-last { + order: 9999; + } + + .xl\:order-none { + order: 0; + } + + .xl\:float-right { + float: right; + } + + .xl\:float-left { + float: left; + } + + .xl\:float-none { + float: none; + } + + .xl\:clearfix:after { + content: ""; + display: table; + clear: both; + } + + .xl\:clear-left { + clear: left; + } + + .xl\:clear-right { + clear: right; + } + + .xl\:clear-both { + clear: both; + } + + .xl\:clear-none { + clear: none; + } + + .xl\:font-sans { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + } + + .xl\:font-serif { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + } + + .xl\:font-mono { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } + + .xl\:font-hairline { + font-weight: 100; + } + + .xl\:font-thin { + font-weight: 200; + } + + .xl\:font-light { + font-weight: 300; + } + + .xl\:font-normal { + font-weight: 400; + } + + .xl\:font-medium { + font-weight: 500; + } + + .xl\:font-semibold { + font-weight: 600; + } + + .xl\:font-bold { + font-weight: 700; + } + + .xl\:font-extrabold { + font-weight: 800; + } + + .xl\:font-black { + font-weight: 900; + } + + .xl\:hover\:font-hairline:hover { + font-weight: 100; + } + + .xl\:hover\:font-thin:hover { + font-weight: 200; + } + + .xl\:hover\:font-light:hover { + font-weight: 300; + } + + .xl\:hover\:font-normal:hover { + font-weight: 400; + } + + .xl\:hover\:font-medium:hover { + font-weight: 500; + } + + .xl\:hover\:font-semibold:hover { + font-weight: 600; + } + + .xl\:hover\:font-bold:hover { + font-weight: 700; + } + + .xl\:hover\:font-extrabold:hover { + font-weight: 800; + } + + .xl\:hover\:font-black:hover { + font-weight: 900; + } + + .xl\:focus\:font-hairline:focus { + font-weight: 100; + } + + .xl\:focus\:font-thin:focus { + font-weight: 200; + } + + .xl\:focus\:font-light:focus { + font-weight: 300; + } + + .xl\:focus\:font-normal:focus { + font-weight: 400; + } + + .xl\:focus\:font-medium:focus { + font-weight: 500; + } + + .xl\:focus\:font-semibold:focus { + font-weight: 600; + } + + .xl\:focus\:font-bold:focus { + font-weight: 700; + } + + .xl\:focus\:font-extrabold:focus { + font-weight: 800; + } + + .xl\:focus\:font-black:focus { + font-weight: 900; + } + + .xl\:h-0 { + height: 0; + } + + .xl\:h-1 { + height: 0.25rem; + } + + .xl\:h-2 { + height: 0.5rem; + } + + .xl\:h-3 { + height: 0.75rem; + } + + .xl\:h-4 { + height: 1rem; + } + + .xl\:h-5 { + height: 1.25rem; + } + + .xl\:h-6 { + height: 1.5rem; + } + + .xl\:h-8 { + height: 2rem; + } + + .xl\:h-10 { + height: 2.5rem; + } + + .xl\:h-12 { + height: 3rem; + } + + .xl\:h-16 { + height: 4rem; + } + + .xl\:h-20 { + height: 5rem; + } + + .xl\:h-24 { + height: 6rem; + } + + .xl\:h-32 { + height: 8rem; + } + + .xl\:h-40 { + height: 10rem; + } + + .xl\:h-48 { + height: 12rem; + } + + .xl\:h-56 { + height: 14rem; + } + + .xl\:h-64 { + height: 16rem; + } + + .xl\:h-auto { + height: auto; + } + + .xl\:h-px { + height: 1px; + } + + .xl\:h-full { + height: 100%; + } + + .xl\:h-screen { + height: 100vh; + } + + .xl\:text-xs { + font-size: 0.75rem; + } + + .xl\:text-sm { + font-size: 0.875rem; + } + + .xl\:text-base { + font-size: 1rem; + } + + .xl\:text-lg { + font-size: 1.125rem; + } + + .xl\:text-xl { + font-size: 1.25rem; + } + + .xl\:text-2xl { + font-size: 1.5rem; + } + + .xl\:text-3xl { + font-size: 1.875rem; + } + + .xl\:text-4xl { + font-size: 2.25rem; + } + + .xl\:text-5xl { + font-size: 3rem; + } + + .xl\:text-6xl { + font-size: 4rem; + } + + .xl\:leading-3 { + line-height: .75rem; + } + + .xl\:leading-4 { + line-height: 1rem; + } + + .xl\:leading-5 { + line-height: 1.25rem; + } + + .xl\:leading-6 { + line-height: 1.5rem; + } + + .xl\:leading-7 { + line-height: 1.75rem; + } + + .xl\:leading-8 { + line-height: 2rem; + } + + .xl\:leading-9 { + line-height: 2.25rem; + } + + .xl\:leading-10 { + line-height: 2.5rem; + } + + .xl\:leading-none { + line-height: 1; + } + + .xl\:leading-tight { + line-height: 1.25; + } + + .xl\:leading-snug { + line-height: 1.375; + } + + .xl\:leading-normal { + line-height: 1.5; + } + + .xl\:leading-relaxed { + line-height: 1.625; + } + + .xl\:leading-loose { + line-height: 2; + } + + .xl\:list-inside { + list-style-position: inside; + } + + .xl\:list-outside { + list-style-position: outside; + } + + .xl\:list-none { + list-style-type: none; + } + + .xl\:list-disc { + list-style-type: disc; + } + + .xl\:list-decimal { + list-style-type: decimal; + } + + .xl\:m-0 { + margin: 0; + } + + .xl\:m-1 { + margin: 0.25rem; + } + + .xl\:m-2 { + margin: 0.5rem; + } + + .xl\:m-3 { + margin: 0.75rem; + } + + .xl\:m-4 { + margin: 1rem; + } + + .xl\:m-5 { + margin: 1.25rem; + } + + .xl\:m-6 { + margin: 1.5rem; + } + + .xl\:m-8 { + margin: 2rem; + } + + .xl\:m-10 { + margin: 2.5rem; + } + + .xl\:m-12 { + margin: 3rem; + } + + .xl\:m-16 { + margin: 4rem; + } + + .xl\:m-20 { + margin: 5rem; + } + + .xl\:m-24 { + margin: 6rem; + } + + .xl\:m-32 { + margin: 8rem; + } + + .xl\:m-40 { + margin: 10rem; + } + + .xl\:m-48 { + margin: 12rem; + } + + .xl\:m-56 { + margin: 14rem; + } + + .xl\:m-64 { + margin: 16rem; + } + + .xl\:m-auto { + margin: auto; + } + + .xl\:m-px { + margin: 1px; + } + + .xl\:-m-1 { + margin: -0.25rem; + } + + .xl\:-m-2 { + margin: -0.5rem; + } + + .xl\:-m-3 { + margin: -0.75rem; + } + + .xl\:-m-4 { + margin: -1rem; + } + + .xl\:-m-5 { + margin: -1.25rem; + } + + .xl\:-m-6 { + margin: -1.5rem; + } + + .xl\:-m-8 { + margin: -2rem; + } + + .xl\:-m-10 { + margin: -2.5rem; + } + + .xl\:-m-12 { + margin: -3rem; + } + + .xl\:-m-16 { + margin: -4rem; + } + + .xl\:-m-20 { + margin: -5rem; + } + + .xl\:-m-24 { + margin: -6rem; + } + + .xl\:-m-32 { + margin: -8rem; + } + + .xl\:-m-40 { + margin: -10rem; + } + + .xl\:-m-48 { + margin: -12rem; + } + + .xl\:-m-56 { + margin: -14rem; + } + + .xl\:-m-64 { + margin: -16rem; + } + + .xl\:-m-px { + margin: -1px; + } + + .xl\:my-0 { + margin-top: 0; + margin-bottom: 0; + } + + .xl\:mx-0 { + margin-left: 0; + margin-right: 0; + } + + .xl\:my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + } + + .xl\:mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + + .xl\:my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + .xl\:mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + + .xl\:my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + } + + .xl\:mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; + } + + .xl\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + .xl\:mx-4 { + margin-left: 1rem; + margin-right: 1rem; + } + + .xl\:my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + } + + .xl\:mx-5 { + margin-left: 1.25rem; + margin-right: 1.25rem; + } + + .xl\:my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + + .xl\:mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; + } + + .xl\:my-8 { + margin-top: 2rem; + margin-bottom: 2rem; + } + + .xl\:mx-8 { + margin-left: 2rem; + margin-right: 2rem; + } + + .xl\:my-10 { + margin-top: 2.5rem; + margin-bottom: 2.5rem; + } + + .xl\:mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; + } + + .xl\:my-12 { + margin-top: 3rem; + margin-bottom: 3rem; + } + + .xl\:mx-12 { + margin-left: 3rem; + margin-right: 3rem; + } + + .xl\:my-16 { + margin-top: 4rem; + margin-bottom: 4rem; + } + + .xl\:mx-16 { + margin-left: 4rem; + margin-right: 4rem; + } + + .xl\:my-20 { + margin-top: 5rem; + margin-bottom: 5rem; + } + + .xl\:mx-20 { + margin-left: 5rem; + margin-right: 5rem; + } + + .xl\:my-24 { + margin-top: 6rem; + margin-bottom: 6rem; + } + + .xl\:mx-24 { + margin-left: 6rem; + margin-right: 6rem; + } + + .xl\:my-32 { + margin-top: 8rem; + margin-bottom: 8rem; + } + + .xl\:mx-32 { + margin-left: 8rem; + margin-right: 8rem; + } + + .xl\:my-40 { + margin-top: 10rem; + margin-bottom: 10rem; + } + + .xl\:mx-40 { + margin-left: 10rem; + margin-right: 10rem; + } + + .xl\:my-48 { + margin-top: 12rem; + margin-bottom: 12rem; + } + + .xl\:mx-48 { + margin-left: 12rem; + margin-right: 12rem; + } + + .xl\:my-56 { + margin-top: 14rem; + margin-bottom: 14rem; + } + + .xl\:mx-56 { + margin-left: 14rem; + margin-right: 14rem; + } + + .xl\:my-64 { + margin-top: 16rem; + margin-bottom: 16rem; + } + + .xl\:mx-64 { + margin-left: 16rem; + margin-right: 16rem; + } + + .xl\:my-auto { + margin-top: auto; + margin-bottom: auto; + } + + .xl\:mx-auto { + margin-left: auto; + margin-right: auto; + } + + .xl\:my-px { + margin-top: 1px; + margin-bottom: 1px; + } + + .xl\:mx-px { + margin-left: 1px; + margin-right: 1px; + } + + .xl\:-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; + } + + .xl\:-mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; + } + + .xl\:-my-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; + } + + .xl\:-mx-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; + } + + .xl\:-my-3 { + margin-top: -0.75rem; + margin-bottom: -0.75rem; + } + + .xl\:-mx-3 { + margin-left: -0.75rem; + margin-right: -0.75rem; + } + + .xl\:-my-4 { + margin-top: -1rem; + margin-bottom: -1rem; + } + + .xl\:-mx-4 { + margin-left: -1rem; + margin-right: -1rem; + } + + .xl\:-my-5 { + margin-top: -1.25rem; + margin-bottom: -1.25rem; + } + + .xl\:-mx-5 { + margin-left: -1.25rem; + margin-right: -1.25rem; + } + + .xl\:-my-6 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; + } + + .xl\:-mx-6 { + margin-left: -1.5rem; + margin-right: -1.5rem; + } + + .xl\:-my-8 { + margin-top: -2rem; + margin-bottom: -2rem; + } + + .xl\:-mx-8 { + margin-left: -2rem; + margin-right: -2rem; + } + + .xl\:-my-10 { + margin-top: -2.5rem; + margin-bottom: -2.5rem; + } + + .xl\:-mx-10 { + margin-left: -2.5rem; + margin-right: -2.5rem; + } + + .xl\:-my-12 { + margin-top: -3rem; + margin-bottom: -3rem; + } + + .xl\:-mx-12 { + margin-left: -3rem; + margin-right: -3rem; + } + + .xl\:-my-16 { + margin-top: -4rem; + margin-bottom: -4rem; + } + + .xl\:-mx-16 { + margin-left: -4rem; + margin-right: -4rem; + } + + .xl\:-my-20 { + margin-top: -5rem; + margin-bottom: -5rem; + } + + .xl\:-mx-20 { + margin-left: -5rem; + margin-right: -5rem; + } + + .xl\:-my-24 { + margin-top: -6rem; + margin-bottom: -6rem; + } + + .xl\:-mx-24 { + margin-left: -6rem; + margin-right: -6rem; + } + + .xl\:-my-32 { + margin-top: -8rem; + margin-bottom: -8rem; + } + + .xl\:-mx-32 { + margin-left: -8rem; + margin-right: -8rem; + } + + .xl\:-my-40 { + margin-top: -10rem; + margin-bottom: -10rem; + } + + .xl\:-mx-40 { + margin-left: -10rem; + margin-right: -10rem; + } + + .xl\:-my-48 { + margin-top: -12rem; + margin-bottom: -12rem; + } + + .xl\:-mx-48 { + margin-left: -12rem; + margin-right: -12rem; + } + + .xl\:-my-56 { + margin-top: -14rem; + margin-bottom: -14rem; + } + + .xl\:-mx-56 { + margin-left: -14rem; + margin-right: -14rem; + } + + .xl\:-my-64 { + margin-top: -16rem; + margin-bottom: -16rem; + } + + .xl\:-mx-64 { + margin-left: -16rem; + margin-right: -16rem; + } + + .xl\:-my-px { + margin-top: -1px; + margin-bottom: -1px; + } + + .xl\:-mx-px { + margin-left: -1px; + margin-right: -1px; + } + + .xl\:mt-0 { + margin-top: 0; + } + + .xl\:mr-0 { + margin-right: 0; + } + + .xl\:mb-0 { + margin-bottom: 0; + } + + .xl\:ml-0 { + margin-left: 0; + } + + .xl\:mt-1 { + margin-top: 0.25rem; + } + + .xl\:mr-1 { + margin-right: 0.25rem; + } + + .xl\:mb-1 { + margin-bottom: 0.25rem; + } + + .xl\:ml-1 { + margin-left: 0.25rem; + } + + .xl\:mt-2 { + margin-top: 0.5rem; + } + + .xl\:mr-2 { + margin-right: 0.5rem; + } + + .xl\:mb-2 { + margin-bottom: 0.5rem; + } + + .xl\:ml-2 { + margin-left: 0.5rem; + } + + .xl\:mt-3 { + margin-top: 0.75rem; + } + + .xl\:mr-3 { + margin-right: 0.75rem; + } + + .xl\:mb-3 { + margin-bottom: 0.75rem; + } + + .xl\:ml-3 { + margin-left: 0.75rem; + } + + .xl\:mt-4 { + margin-top: 1rem; + } + + .xl\:mr-4 { + margin-right: 1rem; + } + + .xl\:mb-4 { + margin-bottom: 1rem; + } + + .xl\:ml-4 { + margin-left: 1rem; + } + + .xl\:mt-5 { + margin-top: 1.25rem; + } + + .xl\:mr-5 { + margin-right: 1.25rem; + } + + .xl\:mb-5 { + margin-bottom: 1.25rem; + } + + .xl\:ml-5 { + margin-left: 1.25rem; + } + + .xl\:mt-6 { + margin-top: 1.5rem; + } + + .xl\:mr-6 { + margin-right: 1.5rem; + } + + .xl\:mb-6 { + margin-bottom: 1.5rem; + } + + .xl\:ml-6 { + margin-left: 1.5rem; + } + + .xl\:mt-8 { + margin-top: 2rem; + } + + .xl\:mr-8 { + margin-right: 2rem; + } + + .xl\:mb-8 { + margin-bottom: 2rem; + } + + .xl\:ml-8 { + margin-left: 2rem; + } + + .xl\:mt-10 { + margin-top: 2.5rem; + } + + .xl\:mr-10 { + margin-right: 2.5rem; + } + + .xl\:mb-10 { + margin-bottom: 2.5rem; + } + + .xl\:ml-10 { + margin-left: 2.5rem; + } + + .xl\:mt-12 { + margin-top: 3rem; + } + + .xl\:mr-12 { + margin-right: 3rem; + } + + .xl\:mb-12 { + margin-bottom: 3rem; + } + + .xl\:ml-12 { + margin-left: 3rem; + } + + .xl\:mt-16 { + margin-top: 4rem; + } + + .xl\:mr-16 { + margin-right: 4rem; + } + + .xl\:mb-16 { + margin-bottom: 4rem; + } + + .xl\:ml-16 { + margin-left: 4rem; + } + + .xl\:mt-20 { + margin-top: 5rem; + } + + .xl\:mr-20 { + margin-right: 5rem; + } + + .xl\:mb-20 { + margin-bottom: 5rem; + } + + .xl\:ml-20 { + margin-left: 5rem; + } + + .xl\:mt-24 { + margin-top: 6rem; + } + + .xl\:mr-24 { + margin-right: 6rem; + } + + .xl\:mb-24 { + margin-bottom: 6rem; + } + + .xl\:ml-24 { + margin-left: 6rem; + } + + .xl\:mt-32 { + margin-top: 8rem; + } + + .xl\:mr-32 { + margin-right: 8rem; + } + + .xl\:mb-32 { + margin-bottom: 8rem; + } + + .xl\:ml-32 { + margin-left: 8rem; + } + + .xl\:mt-40 { + margin-top: 10rem; + } + + .xl\:mr-40 { + margin-right: 10rem; + } + + .xl\:mb-40 { + margin-bottom: 10rem; + } + + .xl\:ml-40 { + margin-left: 10rem; + } + + .xl\:mt-48 { + margin-top: 12rem; + } + + .xl\:mr-48 { + margin-right: 12rem; + } + + .xl\:mb-48 { + margin-bottom: 12rem; + } + + .xl\:ml-48 { + margin-left: 12rem; + } + + .xl\:mt-56 { + margin-top: 14rem; + } + + .xl\:mr-56 { + margin-right: 14rem; + } + + .xl\:mb-56 { + margin-bottom: 14rem; + } + + .xl\:ml-56 { + margin-left: 14rem; + } + + .xl\:mt-64 { + margin-top: 16rem; + } + + .xl\:mr-64 { + margin-right: 16rem; + } + + .xl\:mb-64 { + margin-bottom: 16rem; + } + + .xl\:ml-64 { + margin-left: 16rem; + } + + .xl\:mt-auto { + margin-top: auto; + } + + .xl\:mr-auto { + margin-right: auto; + } + + .xl\:mb-auto { + margin-bottom: auto; + } + + .xl\:ml-auto { + margin-left: auto; + } + + .xl\:mt-px { + margin-top: 1px; + } + + .xl\:mr-px { + margin-right: 1px; + } + + .xl\:mb-px { + margin-bottom: 1px; + } + + .xl\:ml-px { + margin-left: 1px; + } + + .xl\:-mt-1 { + margin-top: -0.25rem; + } + + .xl\:-mr-1 { + margin-right: -0.25rem; + } + + .xl\:-mb-1 { + margin-bottom: -0.25rem; + } + + .xl\:-ml-1 { + margin-left: -0.25rem; + } + + .xl\:-mt-2 { + margin-top: -0.5rem; + } + + .xl\:-mr-2 { + margin-right: -0.5rem; + } + + .xl\:-mb-2 { + margin-bottom: -0.5rem; + } + + .xl\:-ml-2 { + margin-left: -0.5rem; + } + + .xl\:-mt-3 { + margin-top: -0.75rem; + } + + .xl\:-mr-3 { + margin-right: -0.75rem; + } + + .xl\:-mb-3 { + margin-bottom: -0.75rem; + } + + .xl\:-ml-3 { + margin-left: -0.75rem; + } + + .xl\:-mt-4 { + margin-top: -1rem; + } + + .xl\:-mr-4 { + margin-right: -1rem; + } + + .xl\:-mb-4 { + margin-bottom: -1rem; + } + + .xl\:-ml-4 { + margin-left: -1rem; + } + + .xl\:-mt-5 { + margin-top: -1.25rem; + } + + .xl\:-mr-5 { + margin-right: -1.25rem; + } + + .xl\:-mb-5 { + margin-bottom: -1.25rem; + } + + .xl\:-ml-5 { + margin-left: -1.25rem; + } + + .xl\:-mt-6 { + margin-top: -1.5rem; + } + + .xl\:-mr-6 { + margin-right: -1.5rem; + } + + .xl\:-mb-6 { + margin-bottom: -1.5rem; + } + + .xl\:-ml-6 { + margin-left: -1.5rem; + } + + .xl\:-mt-8 { + margin-top: -2rem; + } + + .xl\:-mr-8 { + margin-right: -2rem; + } + + .xl\:-mb-8 { + margin-bottom: -2rem; + } + + .xl\:-ml-8 { + margin-left: -2rem; + } + + .xl\:-mt-10 { + margin-top: -2.5rem; + } + + .xl\:-mr-10 { + margin-right: -2.5rem; + } + + .xl\:-mb-10 { + margin-bottom: -2.5rem; + } + + .xl\:-ml-10 { + margin-left: -2.5rem; + } + + .xl\:-mt-12 { + margin-top: -3rem; + } + + .xl\:-mr-12 { + margin-right: -3rem; + } + + .xl\:-mb-12 { + margin-bottom: -3rem; + } + + .xl\:-ml-12 { + margin-left: -3rem; + } + + .xl\:-mt-16 { + margin-top: -4rem; + } + + .xl\:-mr-16 { + margin-right: -4rem; + } + + .xl\:-mb-16 { + margin-bottom: -4rem; + } + + .xl\:-ml-16 { + margin-left: -4rem; + } + + .xl\:-mt-20 { + margin-top: -5rem; + } + + .xl\:-mr-20 { + margin-right: -5rem; + } + + .xl\:-mb-20 { + margin-bottom: -5rem; + } + + .xl\:-ml-20 { + margin-left: -5rem; + } + + .xl\:-mt-24 { + margin-top: -6rem; + } + + .xl\:-mr-24 { + margin-right: -6rem; + } + + .xl\:-mb-24 { + margin-bottom: -6rem; + } + + .xl\:-ml-24 { + margin-left: -6rem; + } + + .xl\:-mt-32 { + margin-top: -8rem; + } + + .xl\:-mr-32 { + margin-right: -8rem; + } + + .xl\:-mb-32 { + margin-bottom: -8rem; + } + + .xl\:-ml-32 { + margin-left: -8rem; + } + + .xl\:-mt-40 { + margin-top: -10rem; + } + + .xl\:-mr-40 { + margin-right: -10rem; + } + + .xl\:-mb-40 { + margin-bottom: -10rem; + } + + .xl\:-ml-40 { + margin-left: -10rem; + } + + .xl\:-mt-48 { + margin-top: -12rem; + } + + .xl\:-mr-48 { + margin-right: -12rem; + } + + .xl\:-mb-48 { + margin-bottom: -12rem; + } + + .xl\:-ml-48 { + margin-left: -12rem; + } + + .xl\:-mt-56 { + margin-top: -14rem; + } + + .xl\:-mr-56 { + margin-right: -14rem; + } + + .xl\:-mb-56 { + margin-bottom: -14rem; + } + + .xl\:-ml-56 { + margin-left: -14rem; + } + + .xl\:-mt-64 { + margin-top: -16rem; + } + + .xl\:-mr-64 { + margin-right: -16rem; + } + + .xl\:-mb-64 { + margin-bottom: -16rem; + } + + .xl\:-ml-64 { + margin-left: -16rem; + } + + .xl\:-mt-px { + margin-top: -1px; + } + + .xl\:-mr-px { + margin-right: -1px; + } + + .xl\:-mb-px { + margin-bottom: -1px; + } + + .xl\:-ml-px { + margin-left: -1px; + } + + .xl\:max-h-full { + max-height: 100%; + } + + .xl\:max-h-screen { + max-height: 100vh; + } + + .xl\:max-w-none { + max-width: none; + } + + .xl\:max-w-xs { + max-width: 20rem; + } + + .xl\:max-w-sm { + max-width: 24rem; + } + + .xl\:max-w-md { + max-width: 28rem; + } + + .xl\:max-w-lg { + max-width: 32rem; + } + + .xl\:max-w-xl { + max-width: 36rem; + } + + .xl\:max-w-2xl { + max-width: 42rem; + } + + .xl\:max-w-3xl { + max-width: 48rem; + } + + .xl\:max-w-4xl { + max-width: 56rem; + } + + .xl\:max-w-5xl { + max-width: 64rem; + } + + .xl\:max-w-6xl { + max-width: 72rem; + } + + .xl\:max-w-full { + max-width: 100%; + } + + .xl\:max-w-screen-sm { + max-width: 640px; + } + + .xl\:max-w-screen-md { + max-width: 768px; + } + + .xl\:max-w-screen-lg { + max-width: 1024px; + } + + .xl\:max-w-screen-xl { + max-width: 1280px; + } + + .xl\:min-h-0 { + min-height: 0; + } + + .xl\:min-h-full { + min-height: 100%; + } + + .xl\:min-h-screen { + min-height: 100vh; + } + + .xl\:min-w-0 { + min-width: 0; + } + + .xl\:min-w-full { + min-width: 100%; + } + + .xl\:object-contain { + -o-object-fit: contain; + object-fit: contain; + } + + .xl\:object-cover { + -o-object-fit: cover; + object-fit: cover; + } + + .xl\:object-fill { + -o-object-fit: fill; + object-fit: fill; + } + + .xl\:object-none { + -o-object-fit: none; + object-fit: none; + } + + .xl\:object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; + } + + .xl\:object-bottom { + -o-object-position: bottom; + object-position: bottom; + } + + .xl\:object-center { + -o-object-position: center; + object-position: center; + } + + .xl\:object-left { + -o-object-position: left; + object-position: left; + } + + .xl\:object-left-bottom { + -o-object-position: left bottom; + object-position: left bottom; + } + + .xl\:object-left-top { + -o-object-position: left top; + object-position: left top; + } + + .xl\:object-right { + -o-object-position: right; + object-position: right; + } + + .xl\:object-right-bottom { + -o-object-position: right bottom; + object-position: right bottom; + } + + .xl\:object-right-top { + -o-object-position: right top; + object-position: right top; + } + + .xl\:object-top { + -o-object-position: top; + object-position: top; + } + + .xl\:opacity-0 { + opacity: 0; + } + + .xl\:opacity-25 { + opacity: 0.25; + } + + .xl\:opacity-50 { + opacity: 0.5; + } + + .xl\:opacity-75 { + opacity: 0.75; + } + + .xl\:opacity-100 { + opacity: 1; + } + + .xl\:hover\:opacity-0:hover { + opacity: 0; + } + + .xl\:hover\:opacity-25:hover { + opacity: 0.25; + } + + .xl\:hover\:opacity-50:hover { + opacity: 0.5; + } + + .xl\:hover\:opacity-75:hover { + opacity: 0.75; + } + + .xl\:hover\:opacity-100:hover { + opacity: 1; + } + + .xl\:focus\:opacity-0:focus { + opacity: 0; + } + + .xl\:focus\:opacity-25:focus { + opacity: 0.25; + } + + .xl\:focus\:opacity-50:focus { + opacity: 0.5; + } + + .xl\:focus\:opacity-75:focus { + opacity: 0.75; + } + + .xl\:focus\:opacity-100:focus { + opacity: 1; + } + + .xl\:outline-none { + outline: 0; + } + + .xl\:focus\:outline-none:focus { + outline: 0; + } + + .xl\:overflow-auto { + overflow: auto; + } + + .xl\:overflow-hidden { + overflow: hidden; + } + + .xl\:overflow-visible { + overflow: visible; + } + + .xl\:overflow-scroll { + overflow: scroll; + } + + .xl\:overflow-x-auto { + overflow-x: auto; + } + + .xl\:overflow-y-auto { + overflow-y: auto; + } + + .xl\:overflow-x-hidden { + overflow-x: hidden; + } + + .xl\:overflow-y-hidden { + overflow-y: hidden; + } + + .xl\:overflow-x-visible { + overflow-x: visible; + } + + .xl\:overflow-y-visible { + overflow-y: visible; + } + + .xl\:overflow-x-scroll { + overflow-x: scroll; + } + + .xl\:overflow-y-scroll { + overflow-y: scroll; + } + + .xl\:scrolling-touch { + -webkit-overflow-scrolling: touch; + } + + .xl\:scrolling-auto { + -webkit-overflow-scrolling: auto; + } + + .xl\:overscroll-auto { + -ms-scroll-chaining: chained; + overscroll-behavior: auto; + } + + .xl\:overscroll-contain { + -ms-scroll-chaining: none; + overscroll-behavior: contain; + } + + .xl\:overscroll-none { + -ms-scroll-chaining: none; + overscroll-behavior: none; + } + + .xl\:overscroll-y-auto { + overscroll-behavior-y: auto; + } + + .xl\:overscroll-y-contain { + overscroll-behavior-y: contain; + } + + .xl\:overscroll-y-none { + overscroll-behavior-y: none; + } + + .xl\:overscroll-x-auto { + overscroll-behavior-x: auto; + } + + .xl\:overscroll-x-contain { + overscroll-behavior-x: contain; + } + + .xl\:overscroll-x-none { + overscroll-behavior-x: none; + } + + .xl\:p-0 { + padding: 0; + } + + .xl\:p-1 { + padding: 0.25rem; + } + + .xl\:p-2 { + padding: 0.5rem; + } + + .xl\:p-3 { + padding: 0.75rem; + } + + .xl\:p-4 { + padding: 1rem; + } + + .xl\:p-5 { + padding: 1.25rem; + } + + .xl\:p-6 { + padding: 1.5rem; + } + + .xl\:p-8 { + padding: 2rem; + } + + .xl\:p-10 { + padding: 2.5rem; + } + + .xl\:p-12 { + padding: 3rem; + } + + .xl\:p-16 { + padding: 4rem; + } + + .xl\:p-20 { + padding: 5rem; + } + + .xl\:p-24 { + padding: 6rem; + } + + .xl\:p-32 { + padding: 8rem; + } + + .xl\:p-40 { + padding: 10rem; + } + + .xl\:p-48 { + padding: 12rem; + } + + .xl\:p-56 { + padding: 14rem; + } + + .xl\:p-64 { + padding: 16rem; + } + + .xl\:p-px { + padding: 1px; + } + + .xl\:py-0 { + padding-top: 0; + padding-bottom: 0; + } + + .xl\:px-0 { + padding-left: 0; + padding-right: 0; + } + + .xl\:py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } + + .xl\:px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + .xl\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + .xl\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .xl\:py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + + .xl\:px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + + .xl\:py-4 { + padding-top: 1rem; + padding-bottom: 1rem; + } + + .xl\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .xl\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + + .xl\:px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; + } + + .xl\:py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; + } + + .xl\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .xl\:py-8 { + padding-top: 2rem; + padding-bottom: 2rem; + } + + .xl\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .xl\:py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; + } + + .xl\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .xl\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + + .xl\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .xl\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .xl\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .xl\:py-20 { + padding-top: 5rem; + padding-bottom: 5rem; + } + + .xl\:px-20 { + padding-left: 5rem; + padding-right: 5rem; + } + + .xl\:py-24 { + padding-top: 6rem; + padding-bottom: 6rem; + } + + .xl\:px-24 { + padding-left: 6rem; + padding-right: 6rem; + } + + .xl\:py-32 { + padding-top: 8rem; + padding-bottom: 8rem; + } + + .xl\:px-32 { + padding-left: 8rem; + padding-right: 8rem; + } + + .xl\:py-40 { + padding-top: 10rem; + padding-bottom: 10rem; + } + + .xl\:px-40 { + padding-left: 10rem; + padding-right: 10rem; + } + + .xl\:py-48 { + padding-top: 12rem; + padding-bottom: 12rem; + } + + .xl\:px-48 { + padding-left: 12rem; + padding-right: 12rem; + } + + .xl\:py-56 { + padding-top: 14rem; + padding-bottom: 14rem; + } + + .xl\:px-56 { + padding-left: 14rem; + padding-right: 14rem; + } + + .xl\:py-64 { + padding-top: 16rem; + padding-bottom: 16rem; + } + + .xl\:px-64 { + padding-left: 16rem; + padding-right: 16rem; + } + + .xl\:py-px { + padding-top: 1px; + padding-bottom: 1px; + } + + .xl\:px-px { + padding-left: 1px; + padding-right: 1px; + } + + .xl\:pt-0 { + padding-top: 0; + } + + .xl\:pr-0 { + padding-right: 0; + } + + .xl\:pb-0 { + padding-bottom: 0; + } + + .xl\:pl-0 { + padding-left: 0; + } + + .xl\:pt-1 { + padding-top: 0.25rem; + } + + .xl\:pr-1 { + padding-right: 0.25rem; + } + + .xl\:pb-1 { + padding-bottom: 0.25rem; + } + + .xl\:pl-1 { + padding-left: 0.25rem; + } + + .xl\:pt-2 { + padding-top: 0.5rem; + } + + .xl\:pr-2 { + padding-right: 0.5rem; + } + + .xl\:pb-2 { + padding-bottom: 0.5rem; + } + + .xl\:pl-2 { + padding-left: 0.5rem; + } + + .xl\:pt-3 { + padding-top: 0.75rem; + } + + .xl\:pr-3 { + padding-right: 0.75rem; + } + + .xl\:pb-3 { + padding-bottom: 0.75rem; + } + + .xl\:pl-3 { + padding-left: 0.75rem; + } + + .xl\:pt-4 { + padding-top: 1rem; + } + + .xl\:pr-4 { + padding-right: 1rem; + } + + .xl\:pb-4 { + padding-bottom: 1rem; + } + + .xl\:pl-4 { + padding-left: 1rem; + } + + .xl\:pt-5 { + padding-top: 1.25rem; + } + + .xl\:pr-5 { + padding-right: 1.25rem; + } + + .xl\:pb-5 { + padding-bottom: 1.25rem; + } + + .xl\:pl-5 { + padding-left: 1.25rem; + } + + .xl\:pt-6 { + padding-top: 1.5rem; + } + + .xl\:pr-6 { + padding-right: 1.5rem; + } + + .xl\:pb-6 { + padding-bottom: 1.5rem; + } + + .xl\:pl-6 { + padding-left: 1.5rem; + } + + .xl\:pt-8 { + padding-top: 2rem; + } + + .xl\:pr-8 { + padding-right: 2rem; + } + + .xl\:pb-8 { + padding-bottom: 2rem; + } + + .xl\:pl-8 { + padding-left: 2rem; + } + + .xl\:pt-10 { + padding-top: 2.5rem; + } + + .xl\:pr-10 { + padding-right: 2.5rem; + } + + .xl\:pb-10 { + padding-bottom: 2.5rem; + } + + .xl\:pl-10 { + padding-left: 2.5rem; + } + + .xl\:pt-12 { + padding-top: 3rem; + } + + .xl\:pr-12 { + padding-right: 3rem; + } + + .xl\:pb-12 { + padding-bottom: 3rem; + } + + .xl\:pl-12 { + padding-left: 3rem; + } + + .xl\:pt-16 { + padding-top: 4rem; + } + + .xl\:pr-16 { + padding-right: 4rem; + } + + .xl\:pb-16 { + padding-bottom: 4rem; + } + + .xl\:pl-16 { + padding-left: 4rem; + } + + .xl\:pt-20 { + padding-top: 5rem; + } + + .xl\:pr-20 { + padding-right: 5rem; + } + + .xl\:pb-20 { + padding-bottom: 5rem; + } + + .xl\:pl-20 { + padding-left: 5rem; + } + + .xl\:pt-24 { + padding-top: 6rem; + } + + .xl\:pr-24 { + padding-right: 6rem; + } + + .xl\:pb-24 { + padding-bottom: 6rem; + } + + .xl\:pl-24 { + padding-left: 6rem; + } + + .xl\:pt-32 { + padding-top: 8rem; + } + + .xl\:pr-32 { + padding-right: 8rem; + } + + .xl\:pb-32 { + padding-bottom: 8rem; + } + + .xl\:pl-32 { + padding-left: 8rem; + } + + .xl\:pt-40 { + padding-top: 10rem; + } + + .xl\:pr-40 { + padding-right: 10rem; + } + + .xl\:pb-40 { + padding-bottom: 10rem; + } + + .xl\:pl-40 { + padding-left: 10rem; + } + + .xl\:pt-48 { + padding-top: 12rem; + } + + .xl\:pr-48 { + padding-right: 12rem; + } + + .xl\:pb-48 { + padding-bottom: 12rem; + } + + .xl\:pl-48 { + padding-left: 12rem; + } + + .xl\:pt-56 { + padding-top: 14rem; + } + + .xl\:pr-56 { + padding-right: 14rem; + } + + .xl\:pb-56 { + padding-bottom: 14rem; + } + + .xl\:pl-56 { + padding-left: 14rem; + } + + .xl\:pt-64 { + padding-top: 16rem; + } + + .xl\:pr-64 { + padding-right: 16rem; + } + + .xl\:pb-64 { + padding-bottom: 16rem; + } + + .xl\:pl-64 { + padding-left: 16rem; + } + + .xl\:pt-px { + padding-top: 1px; + } + + .xl\:pr-px { + padding-right: 1px; + } + + .xl\:pb-px { + padding-bottom: 1px; + } + + .xl\:pl-px { + padding-left: 1px; + } + + .xl\:placeholder-transparent::-moz-placeholder { + color: transparent; + } + + .xl\:placeholder-transparent:-ms-input-placeholder { + color: transparent; + } + + .xl\:placeholder-transparent::placeholder { + color: transparent; + } + + .xl\:placeholder-current::-moz-placeholder { + color: currentColor; + } + + .xl\:placeholder-current:-ms-input-placeholder { + color: currentColor; + } + + .xl\:placeholder-current::placeholder { + color: currentColor; + } + + .xl\:placeholder-black::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:placeholder-black:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:placeholder-black::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:placeholder-white::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-white:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-white::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-100::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-200::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-300::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-500::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-600::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-700::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-800::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-gray-900::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-200::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-300::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-400::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-500::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-600::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-700::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-800::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:placeholder-red-900::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-100::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-200::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-300::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-400::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-500::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-600::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-700::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-800::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:placeholder-orange-900::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-100::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-200::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-300::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-400::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-500::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-600::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-700::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-800::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:placeholder-yellow-900::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-100::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-200::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-300::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-400::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-500::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-600::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-700::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-800::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:placeholder-green-900::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-100::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-200::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-300::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-400::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-500::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-600::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-700::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-800::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:placeholder-teal-900::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-100::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-200::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-300::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-400::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-500::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-600::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-700::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-800::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-blue-900::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-100::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-200::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-300::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-400::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-500::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-600::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-700::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-800::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:placeholder-indigo-900::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-100::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-200::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-300::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-400::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-500::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-600::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-700::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-800::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:placeholder-purple-900::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-100::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-100:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-100::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-200::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-200:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-200::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-300::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-300:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-300::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-400::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-400::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-500::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-500:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-500::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-600::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-600:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-600::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-700::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-700:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-700::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-800::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-800:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-800::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-900::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-900:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:placeholder-pink-900::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-transparent:focus::-moz-placeholder { + color: transparent; + } + + .xl\:focus\:placeholder-transparent:focus:-ms-input-placeholder { + color: transparent; + } + + .xl\:focus\:placeholder-transparent:focus::placeholder { + color: transparent; + } + + .xl\:focus\:placeholder-current:focus::-moz-placeholder { + color: currentColor; + } + + .xl\:focus\:placeholder-current:focus:-ms-input-placeholder { + color: currentColor; + } + + .xl\:focus\:placeholder-current:focus::placeholder { + color: currentColor; + } + + .xl\:focus\:placeholder-black:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-black:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-black:focus::placeholder { + --placeholder-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-white:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-white:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-white:focus::placeholder { + --placeholder-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-200:focus::placeholder { + --placeholder-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-300:focus::placeholder { + --placeholder-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-400:focus::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-500:focus::placeholder { + --placeholder-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-600:focus::placeholder { + --placeholder-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-gray-900:focus::placeholder { + --placeholder-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-300:focus::placeholder { + --placeholder-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-400:focus::placeholder { + --placeholder-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-500:focus::placeholder { + --placeholder-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-600:focus::placeholder { + --placeholder-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-red-900:focus::placeholder { + --placeholder-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-200:focus::placeholder { + --placeholder-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-600:focus::placeholder { + --placeholder-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-700:focus::placeholder { + --placeholder-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-800:focus::placeholder { + --placeholder-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-orange-900:focus::placeholder { + --placeholder-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-300:focus::placeholder { + --placeholder-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-800:focus::placeholder { + --placeholder-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-yellow-900:focus::placeholder { + --placeholder-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-100:focus::placeholder { + --placeholder-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-300:focus::placeholder { + --placeholder-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-400:focus::placeholder { + --placeholder-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-500:focus::placeholder { + --placeholder-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-600:focus::placeholder { + --placeholder-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-800:focus::placeholder { + --placeholder-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-green-900:focus::placeholder { + --placeholder-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-100:focus::placeholder { + --placeholder-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-200:focus::placeholder { + --placeholder-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-300:focus::placeholder { + --placeholder-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-400:focus::placeholder { + --placeholder-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-500:focus::placeholder { + --placeholder-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-600:focus::placeholder { + --placeholder-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-800:focus::placeholder { + --placeholder-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-teal-900:focus::placeholder { + --placeholder-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-200:focus::placeholder { + --placeholder-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-300:focus::placeholder { + --placeholder-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-400:focus::placeholder { + --placeholder-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-500:focus::placeholder { + --placeholder-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-600:focus::placeholder { + --placeholder-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-700:focus::placeholder { + --placeholder-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-800:focus::placeholder { + --placeholder-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-blue-900:focus::placeholder { + --placeholder-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-100:focus::placeholder { + --placeholder-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-200:focus::placeholder { + --placeholder-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-300:focus::placeholder { + --placeholder-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-400:focus::placeholder { + --placeholder-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-500:focus::placeholder { + --placeholder-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-600:focus::placeholder { + --placeholder-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-700:focus::placeholder { + --placeholder-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-800:focus::placeholder { + --placeholder-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-indigo-900:focus::placeholder { + --placeholder-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-100:focus::placeholder { + --placeholder-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-200:focus::placeholder { + --placeholder-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-300:focus::placeholder { + --placeholder-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-400:focus::placeholder { + --placeholder-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-500:focus::placeholder { + --placeholder-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-600:focus::placeholder { + --placeholder-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-700:focus::placeholder { + --placeholder-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-800:focus::placeholder { + --placeholder-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-purple-900:focus::placeholder { + --placeholder-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-100:focus::placeholder { + --placeholder-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-200:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-200:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-200:focus::placeholder { + --placeholder-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-300:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-300:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-300:focus::placeholder { + --placeholder-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-400:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-400:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-400:focus::placeholder { + --placeholder-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-500:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-500:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-500:focus::placeholder { + --placeholder-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-600:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-600:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-600:focus::placeholder { + --placeholder-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-700:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-700:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-700:focus::placeholder { + --placeholder-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-800:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-800:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-800:focus::placeholder { + --placeholder-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-900:focus::-moz-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-900:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:focus\:placeholder-pink-900:focus::placeholder { + --placeholder-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--placeholder-opacity)); + } + + .xl\:placeholder-opacity-0::-moz-placeholder { + --placeholder-opacity: 0; + } + + .xl\:placeholder-opacity-0:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .xl\:placeholder-opacity-0::placeholder { + --placeholder-opacity: 0; + } + + .xl\:placeholder-opacity-25::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:placeholder-opacity-25:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:placeholder-opacity-25::placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:placeholder-opacity-50::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:placeholder-opacity-50:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:placeholder-opacity-50::placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:placeholder-opacity-75::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:placeholder-opacity-75:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:placeholder-opacity-75::placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:placeholder-opacity-100::-moz-placeholder { + --placeholder-opacity: 1; + } + + .xl\:placeholder-opacity-100:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .xl\:placeholder-opacity-100::placeholder { + --placeholder-opacity: 1; + } + + .xl\:focus\:placeholder-opacity-0:focus::-moz-placeholder { + --placeholder-opacity: 0; + } + + .xl\:focus\:placeholder-opacity-0:focus:-ms-input-placeholder { + --placeholder-opacity: 0; + } + + .xl\:focus\:placeholder-opacity-0:focus::placeholder { + --placeholder-opacity: 0; + } + + .xl\:focus\:placeholder-opacity-25:focus::-moz-placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:focus\:placeholder-opacity-25:focus:-ms-input-placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:focus\:placeholder-opacity-25:focus::placeholder { + --placeholder-opacity: 0.25; + } + + .xl\:focus\:placeholder-opacity-50:focus::-moz-placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:focus\:placeholder-opacity-50:focus:-ms-input-placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:focus\:placeholder-opacity-50:focus::placeholder { + --placeholder-opacity: 0.5; + } + + .xl\:focus\:placeholder-opacity-75:focus::-moz-placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:focus\:placeholder-opacity-75:focus:-ms-input-placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:focus\:placeholder-opacity-75:focus::placeholder { + --placeholder-opacity: 0.75; + } + + .xl\:focus\:placeholder-opacity-100:focus::-moz-placeholder { + --placeholder-opacity: 1; + } + + .xl\:focus\:placeholder-opacity-100:focus:-ms-input-placeholder { + --placeholder-opacity: 1; + } + + .xl\:focus\:placeholder-opacity-100:focus::placeholder { + --placeholder-opacity: 1; + } + + .xl\:pointer-events-none { + pointer-events: none; + } + + .xl\:pointer-events-auto { + pointer-events: auto; + } + + .xl\:static { + position: static; + } + + .xl\:fixed { + position: fixed; + } + + .xl\:absolute { + position: absolute; + } + + .xl\:relative { + position: relative; + } + + .xl\:sticky { + position: -webkit-sticky; + position: sticky; + } + + .xl\:inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .xl\:inset-auto { + top: auto; + right: auto; + bottom: auto; + left: auto; + } + + .xl\:inset-y-0 { + top: 0; + bottom: 0; + } + + .xl\:inset-x-0 { + right: 0; + left: 0; + } + + .xl\:inset-y-auto { + top: auto; + bottom: auto; + } + + .xl\:inset-x-auto { + right: auto; + left: auto; + } + + .xl\:top-0 { + top: 0; + } + + .xl\:right-0 { + right: 0; + } + + .xl\:bottom-0 { + bottom: 0; + } + + .xl\:left-0 { + left: 0; + } + + .xl\:top-auto { + top: auto; + } + + .xl\:right-auto { + right: auto; + } + + .xl\:bottom-auto { + bottom: auto; + } + + .xl\:left-auto { + left: auto; + } + + .xl\:resize-none { + resize: none; + } + + .xl\:resize-y { + resize: vertical; + } + + .xl\:resize-x { + resize: horizontal; + } + + .xl\:resize { + resize: both; + } + + .xl\:shadow-xs { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .xl\:shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .xl\:shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .xl\:shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .xl\:shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .xl\:shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .xl\:shadow-inner { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:shadow-outline { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .xl\:shadow-none { + box-shadow: none; + } + + .xl\:hover\:shadow-xs:hover { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .xl\:hover\:shadow-sm:hover { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .xl\:hover\:shadow:hover { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .xl\:hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .xl\:hover\:shadow-xl:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .xl\:hover\:shadow-2xl:hover { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .xl\:hover\:shadow-inner:hover { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:hover\:shadow-outline:hover { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .xl\:hover\:shadow-none:hover { + box-shadow: none; + } + + .xl\:focus\:shadow-xs:focus { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); + } + + .xl\:focus\:shadow-sm:focus { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + .xl\:focus\:shadow:focus { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:focus\:shadow-md:focus { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + } + + .xl\:focus\:shadow-lg:focus { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .xl\:focus\:shadow-xl:focus { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + .xl\:focus\:shadow-2xl:focus { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .xl\:focus\:shadow-inner:focus { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); + } + + .xl\:focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + } + + .xl\:focus\:shadow-none:focus { + box-shadow: none; + } + + .xl\:fill-current { + fill: currentColor; + } + + .xl\:stroke-current { + stroke: currentColor; + } + + .xl\:stroke-0 { + stroke-width: 0; + } + + .xl\:stroke-1 { + stroke-width: 1; + } + + .xl\:stroke-2 { + stroke-width: 2; + } + + .xl\:table-auto { + table-layout: auto; + } + + .xl\:table-fixed { + table-layout: fixed; + } + + .xl\:text-left { + text-align: left; + } + + .xl\:text-center { + text-align: center; + } + + .xl\:text-right { + text-align: right; + } + + .xl\:text-justify { + text-align: justify; + } + + .xl\:text-transparent { + color: transparent; + } + + .xl\:text-current { + color: currentColor; + } + + .xl\:text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .xl\:text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .xl\:text-gray-100 { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .xl\:text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .xl\:text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .xl\:text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .xl\:text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .xl\:text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .xl\:text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .xl\:text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .xl\:text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .xl\:text-red-100 { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .xl\:text-red-200 { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .xl\:text-red-300 { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .xl\:text-red-400 { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .xl\:text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .xl\:text-red-600 { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .xl\:text-red-700 { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .xl\:text-red-800 { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .xl\:text-red-900 { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .xl\:text-orange-100 { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .xl\:text-orange-200 { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .xl\:text-orange-300 { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .xl\:text-orange-400 { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .xl\:text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .xl\:text-orange-600 { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .xl\:text-orange-700 { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .xl\:text-orange-800 { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .xl\:text-orange-900 { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .xl\:text-yellow-100 { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .xl\:text-yellow-200 { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .xl\:text-yellow-300 { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .xl\:text-yellow-400 { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .xl\:text-yellow-500 { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .xl\:text-yellow-600 { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .xl\:text-yellow-700 { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .xl\:text-yellow-800 { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .xl\:text-yellow-900 { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .xl\:text-green-100 { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .xl\:text-green-200 { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .xl\:text-green-300 { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .xl\:text-green-400 { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .xl\:text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .xl\:text-green-600 { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .xl\:text-green-700 { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .xl\:text-green-800 { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .xl\:text-green-900 { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .xl\:text-teal-100 { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .xl\:text-teal-200 { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .xl\:text-teal-300 { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .xl\:text-teal-400 { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .xl\:text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .xl\:text-teal-600 { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .xl\:text-teal-700 { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .xl\:text-teal-800 { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .xl\:text-teal-900 { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .xl\:text-blue-100 { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .xl\:text-blue-200 { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .xl\:text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .xl\:text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .xl\:text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .xl\:text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .xl\:text-blue-700 { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .xl\:text-blue-800 { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .xl\:text-blue-900 { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .xl\:text-indigo-100 { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .xl\:text-indigo-200 { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .xl\:text-indigo-300 { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .xl\:text-indigo-400 { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .xl\:text-indigo-500 { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .xl\:text-indigo-600 { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .xl\:text-indigo-700 { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .xl\:text-indigo-800 { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .xl\:text-indigo-900 { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .xl\:text-purple-100 { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .xl\:text-purple-200 { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .xl\:text-purple-300 { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .xl\:text-purple-400 { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .xl\:text-purple-500 { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .xl\:text-purple-600 { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .xl\:text-purple-700 { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .xl\:text-purple-800 { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .xl\:text-purple-900 { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .xl\:text-pink-100 { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .xl\:text-pink-200 { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .xl\:text-pink-300 { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .xl\:text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .xl\:text-pink-500 { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .xl\:text-pink-600 { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .xl\:text-pink-700 { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .xl\:text-pink-800 { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .xl\:text-pink-900 { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .xl\:hover\:text-transparent:hover { + color: transparent; + } + + .xl\:hover\:text-current:hover { + color: currentColor; + } + + .xl\:hover\:text-black:hover { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .xl\:hover\:text-white:hover { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .xl\:hover\:text-gray-100:hover { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .xl\:hover\:text-gray-200:hover { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .xl\:hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .xl\:hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .xl\:hover\:text-gray-500:hover { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .xl\:hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .xl\:hover\:text-gray-700:hover { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .xl\:hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .xl\:hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .xl\:hover\:text-red-100:hover { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .xl\:hover\:text-red-200:hover { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .xl\:hover\:text-red-300:hover { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .xl\:hover\:text-red-400:hover { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .xl\:hover\:text-red-500:hover { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .xl\:hover\:text-red-600:hover { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .xl\:hover\:text-red-700:hover { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .xl\:hover\:text-red-800:hover { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .xl\:hover\:text-red-900:hover { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .xl\:hover\:text-orange-100:hover { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .xl\:hover\:text-orange-200:hover { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .xl\:hover\:text-orange-300:hover { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .xl\:hover\:text-orange-400:hover { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .xl\:hover\:text-orange-500:hover { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .xl\:hover\:text-orange-600:hover { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .xl\:hover\:text-orange-700:hover { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .xl\:hover\:text-orange-800:hover { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .xl\:hover\:text-orange-900:hover { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-100:hover { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-200:hover { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-300:hover { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-400:hover { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-500:hover { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-600:hover { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-700:hover { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-800:hover { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .xl\:hover\:text-yellow-900:hover { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .xl\:hover\:text-green-100:hover { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .xl\:hover\:text-green-200:hover { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .xl\:hover\:text-green-300:hover { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .xl\:hover\:text-green-400:hover { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .xl\:hover\:text-green-500:hover { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .xl\:hover\:text-green-600:hover { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .xl\:hover\:text-green-700:hover { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .xl\:hover\:text-green-800:hover { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .xl\:hover\:text-green-900:hover { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .xl\:hover\:text-teal-100:hover { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .xl\:hover\:text-teal-200:hover { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .xl\:hover\:text-teal-300:hover { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .xl\:hover\:text-teal-400:hover { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .xl\:hover\:text-teal-500:hover { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .xl\:hover\:text-teal-600:hover { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .xl\:hover\:text-teal-700:hover { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .xl\:hover\:text-teal-800:hover { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .xl\:hover\:text-teal-900:hover { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .xl\:hover\:text-blue-100:hover { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .xl\:hover\:text-blue-200:hover { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .xl\:hover\:text-blue-300:hover { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .xl\:hover\:text-blue-400:hover { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .xl\:hover\:text-blue-500:hover { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .xl\:hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .xl\:hover\:text-blue-700:hover { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .xl\:hover\:text-blue-800:hover { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .xl\:hover\:text-blue-900:hover { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-100:hover { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-200:hover { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-300:hover { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-400:hover { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-500:hover { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-600:hover { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-700:hover { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-800:hover { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .xl\:hover\:text-indigo-900:hover { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .xl\:hover\:text-purple-100:hover { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .xl\:hover\:text-purple-200:hover { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .xl\:hover\:text-purple-300:hover { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .xl\:hover\:text-purple-400:hover { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .xl\:hover\:text-purple-500:hover { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .xl\:hover\:text-purple-600:hover { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .xl\:hover\:text-purple-700:hover { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .xl\:hover\:text-purple-800:hover { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .xl\:hover\:text-purple-900:hover { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .xl\:hover\:text-pink-100:hover { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .xl\:hover\:text-pink-200:hover { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .xl\:hover\:text-pink-300:hover { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .xl\:hover\:text-pink-400:hover { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .xl\:hover\:text-pink-500:hover { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .xl\:hover\:text-pink-600:hover { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .xl\:hover\:text-pink-700:hover { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .xl\:hover\:text-pink-800:hover { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .xl\:hover\:text-pink-900:hover { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .xl\:focus\:text-transparent:focus { + color: transparent; + } + + .xl\:focus\:text-current:focus { + color: currentColor; + } + + .xl\:focus\:text-black:focus { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); + } + + .xl\:focus\:text-white:focus { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .xl\:focus\:text-gray-100:focus { + --text-opacity: 1; + color: #f7fafc; + color: rgba(247, 250, 252, var(--text-opacity)); + } + + .xl\:focus\:text-gray-200:focus { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); + } + + .xl\:focus\:text-gray-300:focus { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .xl\:focus\:text-gray-400:focus { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); + } + + .xl\:focus\:text-gray-500:focus { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); + } + + .xl\:focus\:text-gray-600:focus { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); + } + + .xl\:focus\:text-gray-700:focus { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); + } + + .xl\:focus\:text-gray-800:focus { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); + } + + .xl\:focus\:text-gray-900:focus { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); + } + + .xl\:focus\:text-red-100:focus { + --text-opacity: 1; + color: #fff5f5; + color: rgba(255, 245, 245, var(--text-opacity)); + } + + .xl\:focus\:text-red-200:focus { + --text-opacity: 1; + color: #fed7d7; + color: rgba(254, 215, 215, var(--text-opacity)); + } + + .xl\:focus\:text-red-300:focus { + --text-opacity: 1; + color: #feb2b2; + color: rgba(254, 178, 178, var(--text-opacity)); + } + + .xl\:focus\:text-red-400:focus { + --text-opacity: 1; + color: #fc8181; + color: rgba(252, 129, 129, var(--text-opacity)); + } + + .xl\:focus\:text-red-500:focus { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); + } + + .xl\:focus\:text-red-600:focus { + --text-opacity: 1; + color: #e53e3e; + color: rgba(229, 62, 62, var(--text-opacity)); + } + + .xl\:focus\:text-red-700:focus { + --text-opacity: 1; + color: #c53030; + color: rgba(197, 48, 48, var(--text-opacity)); + } + + .xl\:focus\:text-red-800:focus { + --text-opacity: 1; + color: #9b2c2c; + color: rgba(155, 44, 44, var(--text-opacity)); + } + + .xl\:focus\:text-red-900:focus { + --text-opacity: 1; + color: #742a2a; + color: rgba(116, 42, 42, var(--text-opacity)); + } + + .xl\:focus\:text-orange-100:focus { + --text-opacity: 1; + color: #fffaf0; + color: rgba(255, 250, 240, var(--text-opacity)); + } + + .xl\:focus\:text-orange-200:focus { + --text-opacity: 1; + color: #feebc8; + color: rgba(254, 235, 200, var(--text-opacity)); + } + + .xl\:focus\:text-orange-300:focus { + --text-opacity: 1; + color: #fbd38d; + color: rgba(251, 211, 141, var(--text-opacity)); + } + + .xl\:focus\:text-orange-400:focus { + --text-opacity: 1; + color: #f6ad55; + color: rgba(246, 173, 85, var(--text-opacity)); + } + + .xl\:focus\:text-orange-500:focus { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); + } + + .xl\:focus\:text-orange-600:focus { + --text-opacity: 1; + color: #dd6b20; + color: rgba(221, 107, 32, var(--text-opacity)); + } + + .xl\:focus\:text-orange-700:focus { + --text-opacity: 1; + color: #c05621; + color: rgba(192, 86, 33, var(--text-opacity)); + } + + .xl\:focus\:text-orange-800:focus { + --text-opacity: 1; + color: #9c4221; + color: rgba(156, 66, 33, var(--text-opacity)); + } + + .xl\:focus\:text-orange-900:focus { + --text-opacity: 1; + color: #7b341e; + color: rgba(123, 52, 30, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-100:focus { + --text-opacity: 1; + color: #fffff0; + color: rgba(255, 255, 240, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-200:focus { + --text-opacity: 1; + color: #fefcbf; + color: rgba(254, 252, 191, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-300:focus { + --text-opacity: 1; + color: #faf089; + color: rgba(250, 240, 137, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-400:focus { + --text-opacity: 1; + color: #f6e05e; + color: rgba(246, 224, 94, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-500:focus { + --text-opacity: 1; + color: #ecc94b; + color: rgba(236, 201, 75, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-600:focus { + --text-opacity: 1; + color: #d69e2e; + color: rgba(214, 158, 46, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-700:focus { + --text-opacity: 1; + color: #b7791f; + color: rgba(183, 121, 31, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-800:focus { + --text-opacity: 1; + color: #975a16; + color: rgba(151, 90, 22, var(--text-opacity)); + } + + .xl\:focus\:text-yellow-900:focus { + --text-opacity: 1; + color: #744210; + color: rgba(116, 66, 16, var(--text-opacity)); + } + + .xl\:focus\:text-green-100:focus { + --text-opacity: 1; + color: #f0fff4; + color: rgba(240, 255, 244, var(--text-opacity)); + } + + .xl\:focus\:text-green-200:focus { + --text-opacity: 1; + color: #c6f6d5; + color: rgba(198, 246, 213, var(--text-opacity)); + } + + .xl\:focus\:text-green-300:focus { + --text-opacity: 1; + color: #9ae6b4; + color: rgba(154, 230, 180, var(--text-opacity)); + } + + .xl\:focus\:text-green-400:focus { + --text-opacity: 1; + color: #68d391; + color: rgba(104, 211, 145, var(--text-opacity)); + } + + .xl\:focus\:text-green-500:focus { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); + } + + .xl\:focus\:text-green-600:focus { + --text-opacity: 1; + color: #38a169; + color: rgba(56, 161, 105, var(--text-opacity)); + } + + .xl\:focus\:text-green-700:focus { + --text-opacity: 1; + color: #2f855a; + color: rgba(47, 133, 90, var(--text-opacity)); + } + + .xl\:focus\:text-green-800:focus { + --text-opacity: 1; + color: #276749; + color: rgba(39, 103, 73, var(--text-opacity)); + } + + .xl\:focus\:text-green-900:focus { + --text-opacity: 1; + color: #22543d; + color: rgba(34, 84, 61, var(--text-opacity)); + } + + .xl\:focus\:text-teal-100:focus { + --text-opacity: 1; + color: #e6fffa; + color: rgba(230, 255, 250, var(--text-opacity)); + } + + .xl\:focus\:text-teal-200:focus { + --text-opacity: 1; + color: #b2f5ea; + color: rgba(178, 245, 234, var(--text-opacity)); + } + + .xl\:focus\:text-teal-300:focus { + --text-opacity: 1; + color: #81e6d9; + color: rgba(129, 230, 217, var(--text-opacity)); + } + + .xl\:focus\:text-teal-400:focus { + --text-opacity: 1; + color: #4fd1c5; + color: rgba(79, 209, 197, var(--text-opacity)); + } + + .xl\:focus\:text-teal-500:focus { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); + } + + .xl\:focus\:text-teal-600:focus { + --text-opacity: 1; + color: #319795; + color: rgba(49, 151, 149, var(--text-opacity)); + } + + .xl\:focus\:text-teal-700:focus { + --text-opacity: 1; + color: #2c7a7b; + color: rgba(44, 122, 123, var(--text-opacity)); + } + + .xl\:focus\:text-teal-800:focus { + --text-opacity: 1; + color: #285e61; + color: rgba(40, 94, 97, var(--text-opacity)); + } + + .xl\:focus\:text-teal-900:focus { + --text-opacity: 1; + color: #234e52; + color: rgba(35, 78, 82, var(--text-opacity)); + } + + .xl\:focus\:text-blue-100:focus { + --text-opacity: 1; + color: #ebf8ff; + color: rgba(235, 248, 255, var(--text-opacity)); + } + + .xl\:focus\:text-blue-200:focus { + --text-opacity: 1; + color: #bee3f8; + color: rgba(190, 227, 248, var(--text-opacity)); + } + + .xl\:focus\:text-blue-300:focus { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); + } + + .xl\:focus\:text-blue-400:focus { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); + } + + .xl\:focus\:text-blue-500:focus { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); + } + + .xl\:focus\:text-blue-600:focus { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); + } + + .xl\:focus\:text-blue-700:focus { + --text-opacity: 1; + color: #2b6cb0; + color: rgba(43, 108, 176, var(--text-opacity)); + } + + .xl\:focus\:text-blue-800:focus { + --text-opacity: 1; + color: #2c5282; + color: rgba(44, 82, 130, var(--text-opacity)); + } + + .xl\:focus\:text-blue-900:focus { + --text-opacity: 1; + color: #2a4365; + color: rgba(42, 67, 101, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-100:focus { + --text-opacity: 1; + color: #ebf4ff; + color: rgba(235, 244, 255, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-200:focus { + --text-opacity: 1; + color: #c3dafe; + color: rgba(195, 218, 254, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-300:focus { + --text-opacity: 1; + color: #a3bffa; + color: rgba(163, 191, 250, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-400:focus { + --text-opacity: 1; + color: #7f9cf5; + color: rgba(127, 156, 245, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-500:focus { + --text-opacity: 1; + color: #667eea; + color: rgba(102, 126, 234, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-600:focus { + --text-opacity: 1; + color: #5a67d8; + color: rgba(90, 103, 216, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-700:focus { + --text-opacity: 1; + color: #4c51bf; + color: rgba(76, 81, 191, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-800:focus { + --text-opacity: 1; + color: #434190; + color: rgba(67, 65, 144, var(--text-opacity)); + } + + .xl\:focus\:text-indigo-900:focus { + --text-opacity: 1; + color: #3c366b; + color: rgba(60, 54, 107, var(--text-opacity)); + } + + .xl\:focus\:text-purple-100:focus { + --text-opacity: 1; + color: #faf5ff; + color: rgba(250, 245, 255, var(--text-opacity)); + } + + .xl\:focus\:text-purple-200:focus { + --text-opacity: 1; + color: #e9d8fd; + color: rgba(233, 216, 253, var(--text-opacity)); + } + + .xl\:focus\:text-purple-300:focus { + --text-opacity: 1; + color: #d6bcfa; + color: rgba(214, 188, 250, var(--text-opacity)); + } + + .xl\:focus\:text-purple-400:focus { + --text-opacity: 1; + color: #b794f4; + color: rgba(183, 148, 244, var(--text-opacity)); + } + + .xl\:focus\:text-purple-500:focus { + --text-opacity: 1; + color: #9f7aea; + color: rgba(159, 122, 234, var(--text-opacity)); + } + + .xl\:focus\:text-purple-600:focus { + --text-opacity: 1; + color: #805ad5; + color: rgba(128, 90, 213, var(--text-opacity)); + } + + .xl\:focus\:text-purple-700:focus { + --text-opacity: 1; + color: #6b46c1; + color: rgba(107, 70, 193, var(--text-opacity)); + } + + .xl\:focus\:text-purple-800:focus { + --text-opacity: 1; + color: #553c9a; + color: rgba(85, 60, 154, var(--text-opacity)); + } + + .xl\:focus\:text-purple-900:focus { + --text-opacity: 1; + color: #44337a; + color: rgba(68, 51, 122, var(--text-opacity)); + } + + .xl\:focus\:text-pink-100:focus { + --text-opacity: 1; + color: #fff5f7; + color: rgba(255, 245, 247, var(--text-opacity)); + } + + .xl\:focus\:text-pink-200:focus { + --text-opacity: 1; + color: #fed7e2; + color: rgba(254, 215, 226, var(--text-opacity)); + } + + .xl\:focus\:text-pink-300:focus { + --text-opacity: 1; + color: #fbb6ce; + color: rgba(251, 182, 206, var(--text-opacity)); + } + + .xl\:focus\:text-pink-400:focus { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); + } + + .xl\:focus\:text-pink-500:focus { + --text-opacity: 1; + color: #ed64a6; + color: rgba(237, 100, 166, var(--text-opacity)); + } + + .xl\:focus\:text-pink-600:focus { + --text-opacity: 1; + color: #d53f8c; + color: rgba(213, 63, 140, var(--text-opacity)); + } + + .xl\:focus\:text-pink-700:focus { + --text-opacity: 1; + color: #b83280; + color: rgba(184, 50, 128, var(--text-opacity)); + } + + .xl\:focus\:text-pink-800:focus { + --text-opacity: 1; + color: #97266d; + color: rgba(151, 38, 109, var(--text-opacity)); + } + + .xl\:focus\:text-pink-900:focus { + --text-opacity: 1; + color: #702459; + color: rgba(112, 36, 89, var(--text-opacity)); + } + + .xl\:text-opacity-0 { + --text-opacity: 0; + } + + .xl\:text-opacity-25 { + --text-opacity: 0.25; + } + + .xl\:text-opacity-50 { + --text-opacity: 0.5; + } + + .xl\:text-opacity-75 { + --text-opacity: 0.75; + } + + .xl\:text-opacity-100 { + --text-opacity: 1; + } + + .xl\:hover\:text-opacity-0:hover { + --text-opacity: 0; + } + + .xl\:hover\:text-opacity-25:hover { + --text-opacity: 0.25; + } + + .xl\:hover\:text-opacity-50:hover { + --text-opacity: 0.5; + } + + .xl\:hover\:text-opacity-75:hover { + --text-opacity: 0.75; + } + + .xl\:hover\:text-opacity-100:hover { + --text-opacity: 1; + } + + .xl\:focus\:text-opacity-0:focus { + --text-opacity: 0; + } + + .xl\:focus\:text-opacity-25:focus { + --text-opacity: 0.25; + } + + .xl\:focus\:text-opacity-50:focus { + --text-opacity: 0.5; + } + + .xl\:focus\:text-opacity-75:focus { + --text-opacity: 0.75; + } + + .xl\:focus\:text-opacity-100:focus { + --text-opacity: 1; + } + + .xl\:italic { + font-style: italic; + } + + .xl\:not-italic { + font-style: normal; + } + + .xl\:uppercase { + text-transform: uppercase; + } + + .xl\:lowercase { + text-transform: lowercase; + } + + .xl\:capitalize { + text-transform: capitalize; + } + + .xl\:normal-case { + text-transform: none; + } + + .xl\:underline { + text-decoration: underline; + } + + .xl\:line-through { + text-decoration: line-through; + } + + .xl\:no-underline { + text-decoration: none; + } + + .xl\:hover\:underline:hover { + text-decoration: underline; + } + + .xl\:hover\:line-through:hover { + text-decoration: line-through; + } + + .xl\:hover\:no-underline:hover { + text-decoration: none; + } + + .xl\:focus\:underline:focus { + text-decoration: underline; + } + + .xl\:focus\:line-through:focus { + text-decoration: line-through; + } + + .xl\:focus\:no-underline:focus { + text-decoration: none; + } + + .xl\:antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .xl\:subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + } + + .xl\:ordinal, .xl\:slashed-zero, .xl\:lining-nums, .xl\:oldstyle-nums, .xl\:proportional-nums, .xl\:tabular-nums, .xl\:diagonal-fractions, .xl\:stacked-fractions { + --font-variant-numeric-ordinal: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-slashed-zero: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-figure: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-spacing: var(--tailwind-empty,/*!*/ /*!*/); + --font-variant-numeric-fraction: var(--tailwind-empty,/*!*/ /*!*/); + font-variant-numeric: var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction); + } + + .xl\:normal-nums { + font-variant-numeric: normal; + } + + .xl\:ordinal { + --font-variant-numeric-ordinal: ordinal; + } + + .xl\:slashed-zero { + --font-variant-numeric-slashed-zero: slashed-zero; + } + + .xl\:lining-nums { + --font-variant-numeric-figure: lining-nums; + } + + .xl\:oldstyle-nums { + --font-variant-numeric-figure: oldstyle-nums; + } + + .xl\:proportional-nums { + --font-variant-numeric-spacing: proportional-nums; + } + + .xl\:tabular-nums { + --font-variant-numeric-spacing: tabular-nums; + } + + .xl\:diagonal-fractions { + --font-variant-numeric-fraction: diagonal-fractions; + } + + .xl\:stacked-fractions { + --font-variant-numeric-fraction: stacked-fractions; + } + + .xl\:tracking-tighter { + letter-spacing: -0.05em; + } + + .xl\:tracking-tight { + letter-spacing: -0.025em; + } + + .xl\:tracking-normal { + letter-spacing: 0; + } + + .xl\:tracking-wide { + letter-spacing: 0.025em; + } + + .xl\:tracking-wider { + letter-spacing: 0.05em; + } + + .xl\:tracking-widest { + letter-spacing: 0.1em; + } + + .xl\:select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .xl\:select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .xl\:select-all { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + } + + .xl\:select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto; + } + + .xl\:align-baseline { + vertical-align: baseline; + } + + .xl\:align-top { + vertical-align: top; + } + + .xl\:align-middle { + vertical-align: middle; + } + + .xl\:align-bottom { + vertical-align: bottom; + } + + .xl\:align-text-top { + vertical-align: text-top; + } + + .xl\:align-text-bottom { + vertical-align: text-bottom; + } + + .xl\:visible { + visibility: visible; + } + + .xl\:invisible { + visibility: hidden; + } + + .xl\:whitespace-normal { + white-space: normal; + } + + .xl\:whitespace-no-wrap { + white-space: nowrap; + } + + .xl\:whitespace-pre { + white-space: pre; + } + + .xl\:whitespace-pre-line { + white-space: pre-line; + } + + .xl\:whitespace-pre-wrap { + white-space: pre-wrap; + } + + .xl\:break-normal { + overflow-wrap: normal; + word-break: normal; + } + + .xl\:break-words { + overflow-wrap: break-word; + } + + .xl\:break-all { + word-break: break-all; + } + + .xl\:truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .xl\:w-0 { + width: 0; + } + + .xl\:w-1 { + width: 0.25rem; + } + + .xl\:w-2 { + width: 0.5rem; + } + + .xl\:w-3 { + width: 0.75rem; + } + + .xl\:w-4 { + width: 1rem; + } + + .xl\:w-5 { + width: 1.25rem; + } + + .xl\:w-6 { + width: 1.5rem; + } + + .xl\:w-8 { + width: 2rem; + } + + .xl\:w-10 { + width: 2.5rem; + } + + .xl\:w-12 { + width: 3rem; + } + + .xl\:w-16 { + width: 4rem; + } + + .xl\:w-20 { + width: 5rem; + } + + .xl\:w-24 { + width: 6rem; + } + + .xl\:w-32 { + width: 8rem; + } + + .xl\:w-40 { + width: 10rem; + } + + .xl\:w-48 { + width: 12rem; + } + + .xl\:w-56 { + width: 14rem; + } + + .xl\:w-64 { + width: 16rem; + } + + .xl\:w-auto { + width: auto; + } + + .xl\:w-px { + width: 1px; + } + + .xl\:w-1\/2 { + width: 50%; + } + + .xl\:w-1\/3 { + width: 33.333333%; + } + + .xl\:w-2\/3 { + width: 66.666667%; + } + + .xl\:w-1\/4 { + width: 25%; + } + + .xl\:w-2\/4 { + width: 50%; + } + + .xl\:w-3\/4 { + width: 75%; + } + + .xl\:w-1\/5 { + width: 20%; + } + + .xl\:w-2\/5 { + width: 40%; + } + + .xl\:w-3\/5 { + width: 60%; + } + + .xl\:w-4\/5 { + width: 80%; + } + + .xl\:w-1\/6 { + width: 16.666667%; + } + + .xl\:w-2\/6 { + width: 33.333333%; + } + + .xl\:w-3\/6 { + width: 50%; + } + + .xl\:w-4\/6 { + width: 66.666667%; + } + + .xl\:w-5\/6 { + width: 83.333333%; + } + + .xl\:w-1\/12 { + width: 8.333333%; + } + + .xl\:w-2\/12 { + width: 16.666667%; + } + + .xl\:w-3\/12 { + width: 25%; + } + + .xl\:w-4\/12 { + width: 33.333333%; + } + + .xl\:w-5\/12 { + width: 41.666667%; + } + + .xl\:w-6\/12 { + width: 50%; + } + + .xl\:w-7\/12 { + width: 58.333333%; + } + + .xl\:w-8\/12 { + width: 66.666667%; + } + + .xl\:w-9\/12 { + width: 75%; + } + + .xl\:w-10\/12 { + width: 83.333333%; + } + + .xl\:w-11\/12 { + width: 91.666667%; + } + + .xl\:w-full { + width: 100%; + } + + .xl\:w-screen { + width: 100vw; + } + + .xl\:z-0 { + z-index: 0; + } + + .xl\:z-10 { + z-index: 10; + } + + .xl\:z-20 { + z-index: 20; + } + + .xl\:z-30 { + z-index: 30; + } + + .xl\:z-40 { + z-index: 40; + } + + .xl\:z-50 { + z-index: 50; + } + + .xl\:z-auto { + z-index: auto; + } + + .xl\:gap-0 { + grid-gap: 0; + gap: 0; + } + + .xl\:gap-1 { + grid-gap: 0.25rem; + gap: 0.25rem; + } + + .xl\:gap-2 { + grid-gap: 0.5rem; + gap: 0.5rem; + } + + .xl\:gap-3 { + grid-gap: 0.75rem; + gap: 0.75rem; + } + + .xl\:gap-4 { + grid-gap: 1rem; + gap: 1rem; + } + + .xl\:gap-5 { + grid-gap: 1.25rem; + gap: 1.25rem; + } + + .xl\:gap-6 { + grid-gap: 1.5rem; + gap: 1.5rem; + } + + .xl\:gap-8 { + grid-gap: 2rem; + gap: 2rem; + } + + .xl\:gap-10 { + grid-gap: 2.5rem; + gap: 2.5rem; + } + + .xl\:gap-12 { + grid-gap: 3rem; + gap: 3rem; + } + + .xl\:gap-16 { + grid-gap: 4rem; + gap: 4rem; + } + + .xl\:gap-20 { + grid-gap: 5rem; + gap: 5rem; + } + + .xl\:gap-24 { + grid-gap: 6rem; + gap: 6rem; + } + + .xl\:gap-32 { + grid-gap: 8rem; + gap: 8rem; + } + + .xl\:gap-40 { + grid-gap: 10rem; + gap: 10rem; + } + + .xl\:gap-48 { + grid-gap: 12rem; + gap: 12rem; + } + + .xl\:gap-56 { + grid-gap: 14rem; + gap: 14rem; + } + + .xl\:gap-64 { + grid-gap: 16rem; + gap: 16rem; + } + + .xl\:gap-px { + grid-gap: 1px; + gap: 1px; + } + + .xl\:col-gap-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .xl\:col-gap-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .xl\:col-gap-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .xl\:col-gap-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .xl\:col-gap-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .xl\:col-gap-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .xl\:col-gap-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .xl\:col-gap-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .xl\:col-gap-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .xl\:col-gap-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .xl\:col-gap-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .xl\:col-gap-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .xl\:col-gap-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .xl\:col-gap-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .xl\:col-gap-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .xl\:col-gap-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .xl\:col-gap-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .xl\:col-gap-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .xl\:col-gap-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .xl\:gap-x-0 { + grid-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + } + + .xl\:gap-x-1 { + grid-column-gap: 0.25rem; + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; + } + + .xl\:gap-x-2 { + grid-column-gap: 0.5rem; + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; + } + + .xl\:gap-x-3 { + grid-column-gap: 0.75rem; + -moz-column-gap: 0.75rem; + column-gap: 0.75rem; + } + + .xl\:gap-x-4 { + grid-column-gap: 1rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + } + + .xl\:gap-x-5 { + grid-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + } + + .xl\:gap-x-6 { + grid-column-gap: 1.5rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; + } + + .xl\:gap-x-8 { + grid-column-gap: 2rem; + -moz-column-gap: 2rem; + column-gap: 2rem; + } + + .xl\:gap-x-10 { + grid-column-gap: 2.5rem; + -moz-column-gap: 2.5rem; + column-gap: 2.5rem; + } + + .xl\:gap-x-12 { + grid-column-gap: 3rem; + -moz-column-gap: 3rem; + column-gap: 3rem; + } + + .xl\:gap-x-16 { + grid-column-gap: 4rem; + -moz-column-gap: 4rem; + column-gap: 4rem; + } + + .xl\:gap-x-20 { + grid-column-gap: 5rem; + -moz-column-gap: 5rem; + column-gap: 5rem; + } + + .xl\:gap-x-24 { + grid-column-gap: 6rem; + -moz-column-gap: 6rem; + column-gap: 6rem; + } + + .xl\:gap-x-32 { + grid-column-gap: 8rem; + -moz-column-gap: 8rem; + column-gap: 8rem; + } + + .xl\:gap-x-40 { + grid-column-gap: 10rem; + -moz-column-gap: 10rem; + column-gap: 10rem; + } + + .xl\:gap-x-48 { + grid-column-gap: 12rem; + -moz-column-gap: 12rem; + column-gap: 12rem; + } + + .xl\:gap-x-56 { + grid-column-gap: 14rem; + -moz-column-gap: 14rem; + column-gap: 14rem; + } + + .xl\:gap-x-64 { + grid-column-gap: 16rem; + -moz-column-gap: 16rem; + column-gap: 16rem; + } + + .xl\:gap-x-px { + grid-column-gap: 1px; + -moz-column-gap: 1px; + column-gap: 1px; + } + + .xl\:row-gap-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .xl\:row-gap-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .xl\:row-gap-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .xl\:row-gap-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .xl\:row-gap-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .xl\:row-gap-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .xl\:row-gap-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .xl\:row-gap-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .xl\:row-gap-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .xl\:row-gap-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .xl\:row-gap-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .xl\:row-gap-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .xl\:row-gap-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .xl\:row-gap-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .xl\:row-gap-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .xl\:row-gap-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .xl\:row-gap-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .xl\:row-gap-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .xl\:row-gap-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .xl\:gap-y-0 { + grid-row-gap: 0; + row-gap: 0; + } + + .xl\:gap-y-1 { + grid-row-gap: 0.25rem; + row-gap: 0.25rem; + } + + .xl\:gap-y-2 { + grid-row-gap: 0.5rem; + row-gap: 0.5rem; + } + + .xl\:gap-y-3 { + grid-row-gap: 0.75rem; + row-gap: 0.75rem; + } + + .xl\:gap-y-4 { + grid-row-gap: 1rem; + row-gap: 1rem; + } + + .xl\:gap-y-5 { + grid-row-gap: 1.25rem; + row-gap: 1.25rem; + } + + .xl\:gap-y-6 { + grid-row-gap: 1.5rem; + row-gap: 1.5rem; + } + + .xl\:gap-y-8 { + grid-row-gap: 2rem; + row-gap: 2rem; + } + + .xl\:gap-y-10 { + grid-row-gap: 2.5rem; + row-gap: 2.5rem; + } + + .xl\:gap-y-12 { + grid-row-gap: 3rem; + row-gap: 3rem; + } + + .xl\:gap-y-16 { + grid-row-gap: 4rem; + row-gap: 4rem; + } + + .xl\:gap-y-20 { + grid-row-gap: 5rem; + row-gap: 5rem; + } + + .xl\:gap-y-24 { + grid-row-gap: 6rem; + row-gap: 6rem; + } + + .xl\:gap-y-32 { + grid-row-gap: 8rem; + row-gap: 8rem; + } + + .xl\:gap-y-40 { + grid-row-gap: 10rem; + row-gap: 10rem; + } + + .xl\:gap-y-48 { + grid-row-gap: 12rem; + row-gap: 12rem; + } + + .xl\:gap-y-56 { + grid-row-gap: 14rem; + row-gap: 14rem; + } + + .xl\:gap-y-64 { + grid-row-gap: 16rem; + row-gap: 16rem; + } + + .xl\:gap-y-px { + grid-row-gap: 1px; + row-gap: 1px; + } + + .xl\:grid-flow-row { + grid-auto-flow: row; + } + + .xl\:grid-flow-col { + grid-auto-flow: column; + } + + .xl\:grid-flow-row-dense { + grid-auto-flow: row dense; + } + + .xl\:grid-flow-col-dense { + grid-auto-flow: column dense; + } + + .xl\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .xl\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .xl\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .xl\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .xl\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .xl\:grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + .xl\:grid-cols-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + + .xl\:grid-cols-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + + .xl\:grid-cols-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + + .xl\:grid-cols-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + + .xl\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + + .xl\:grid-cols-none { + grid-template-columns: none; + } + + .xl\:col-auto { + grid-column: auto; + } + + .xl\:col-span-1 { + grid-column: span 1 / span 1; + } + + .xl\:col-span-2 { + grid-column: span 2 / span 2; + } + + .xl\:col-span-3 { + grid-column: span 3 / span 3; + } + + .xl\:col-span-4 { + grid-column: span 4 / span 4; + } + + .xl\:col-span-5 { + grid-column: span 5 / span 5; + } + + .xl\:col-span-6 { + grid-column: span 6 / span 6; + } + + .xl\:col-span-7 { + grid-column: span 7 / span 7; + } + + .xl\:col-span-8 { + grid-column: span 8 / span 8; + } + + .xl\:col-span-9 { + grid-column: span 9 / span 9; + } + + .xl\:col-span-10 { + grid-column: span 10 / span 10; + } + + .xl\:col-span-11 { + grid-column: span 11 / span 11; + } + + .xl\:col-span-12 { + grid-column: span 12 / span 12; + } + + .xl\:col-start-1 { + grid-column-start: 1; + } + + .xl\:col-start-2 { + grid-column-start: 2; + } + + .xl\:col-start-3 { + grid-column-start: 3; + } + + .xl\:col-start-4 { + grid-column-start: 4; + } + + .xl\:col-start-5 { + grid-column-start: 5; + } + + .xl\:col-start-6 { + grid-column-start: 6; + } + + .xl\:col-start-7 { + grid-column-start: 7; + } + + .xl\:col-start-8 { + grid-column-start: 8; + } + + .xl\:col-start-9 { + grid-column-start: 9; + } + + .xl\:col-start-10 { + grid-column-start: 10; + } + + .xl\:col-start-11 { + grid-column-start: 11; + } + + .xl\:col-start-12 { + grid-column-start: 12; + } + + .xl\:col-start-13 { + grid-column-start: 13; + } + + .xl\:col-start-auto { + grid-column-start: auto; + } + + .xl\:col-end-1 { + grid-column-end: 1; + } + + .xl\:col-end-2 { + grid-column-end: 2; + } + + .xl\:col-end-3 { + grid-column-end: 3; + } + + .xl\:col-end-4 { + grid-column-end: 4; + } + + .xl\:col-end-5 { + grid-column-end: 5; + } + + .xl\:col-end-6 { + grid-column-end: 6; + } + + .xl\:col-end-7 { + grid-column-end: 7; + } + + .xl\:col-end-8 { + grid-column-end: 8; + } + + .xl\:col-end-9 { + grid-column-end: 9; + } + + .xl\:col-end-10 { + grid-column-end: 10; + } + + .xl\:col-end-11 { + grid-column-end: 11; + } + + .xl\:col-end-12 { + grid-column-end: 12; + } + + .xl\:col-end-13 { + grid-column-end: 13; + } + + .xl\:col-end-auto { + grid-column-end: auto; + } + + .xl\:grid-rows-1 { + grid-template-rows: repeat(1, minmax(0, 1fr)); + } + + .xl\:grid-rows-2 { + grid-template-rows: repeat(2, minmax(0, 1fr)); + } + + .xl\:grid-rows-3 { + grid-template-rows: repeat(3, minmax(0, 1fr)); + } + + .xl\:grid-rows-4 { + grid-template-rows: repeat(4, minmax(0, 1fr)); + } + + .xl\:grid-rows-5 { + grid-template-rows: repeat(5, minmax(0, 1fr)); + } + + .xl\:grid-rows-6 { + grid-template-rows: repeat(6, minmax(0, 1fr)); + } + + .xl\:grid-rows-none { + grid-template-rows: none; + } + + .xl\:row-auto { + grid-row: auto; + } + + .xl\:row-span-1 { + grid-row: span 1 / span 1; + } + + .xl\:row-span-2 { + grid-row: span 2 / span 2; + } + + .xl\:row-span-3 { + grid-row: span 3 / span 3; + } + + .xl\:row-span-4 { + grid-row: span 4 / span 4; + } + + .xl\:row-span-5 { + grid-row: span 5 / span 5; + } + + .xl\:row-span-6 { + grid-row: span 6 / span 6; + } + + .xl\:row-start-1 { + grid-row-start: 1; + } + + .xl\:row-start-2 { + grid-row-start: 2; + } + + .xl\:row-start-3 { + grid-row-start: 3; + } + + .xl\:row-start-4 { + grid-row-start: 4; + } + + .xl\:row-start-5 { + grid-row-start: 5; + } + + .xl\:row-start-6 { + grid-row-start: 6; + } + + .xl\:row-start-7 { + grid-row-start: 7; + } + + .xl\:row-start-auto { + grid-row-start: auto; + } + + .xl\:row-end-1 { + grid-row-end: 1; + } + + .xl\:row-end-2 { + grid-row-end: 2; + } + + .xl\:row-end-3 { + grid-row-end: 3; + } + + .xl\:row-end-4 { + grid-row-end: 4; + } + + .xl\:row-end-5 { + grid-row-end: 5; + } + + .xl\:row-end-6 { + grid-row-end: 6; + } + + .xl\:row-end-7 { + grid-row-end: 7; + } + + .xl\:row-end-auto { + grid-row-end: auto; + } + + .xl\:transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); + } + + .xl\:transform-none { + transform: none; + } + + .xl\:origin-center { + transform-origin: center; + } + + .xl\:origin-top { + transform-origin: top; + } + + .xl\:origin-top-right { + transform-origin: top right; + } + + .xl\:origin-right { + transform-origin: right; + } + + .xl\:origin-bottom-right { + transform-origin: bottom right; + } + + .xl\:origin-bottom { + transform-origin: bottom; + } + + .xl\:origin-bottom-left { + transform-origin: bottom left; + } + + .xl\:origin-left { + transform-origin: left; + } + + .xl\:origin-top-left { + transform-origin: top left; + } + + .xl\:scale-0 { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .xl\:scale-50 { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .xl\:scale-75 { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .xl\:scale-90 { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .xl\:scale-95 { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .xl\:scale-100 { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .xl\:scale-105 { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .xl\:scale-110 { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .xl\:scale-125 { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .xl\:scale-150 { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .xl\:scale-x-0 { + --transform-scale-x: 0; + } + + .xl\:scale-x-50 { + --transform-scale-x: .5; + } + + .xl\:scale-x-75 { + --transform-scale-x: .75; + } + + .xl\:scale-x-90 { + --transform-scale-x: .9; + } + + .xl\:scale-x-95 { + --transform-scale-x: .95; + } + + .xl\:scale-x-100 { + --transform-scale-x: 1; + } + + .xl\:scale-x-105 { + --transform-scale-x: 1.05; + } + + .xl\:scale-x-110 { + --transform-scale-x: 1.1; + } + + .xl\:scale-x-125 { + --transform-scale-x: 1.25; + } + + .xl\:scale-x-150 { + --transform-scale-x: 1.5; + } + + .xl\:scale-y-0 { + --transform-scale-y: 0; + } + + .xl\:scale-y-50 { + --transform-scale-y: .5; + } + + .xl\:scale-y-75 { + --transform-scale-y: .75; + } + + .xl\:scale-y-90 { + --transform-scale-y: .9; + } + + .xl\:scale-y-95 { + --transform-scale-y: .95; + } + + .xl\:scale-y-100 { + --transform-scale-y: 1; + } + + .xl\:scale-y-105 { + --transform-scale-y: 1.05; + } + + .xl\:scale-y-110 { + --transform-scale-y: 1.1; + } + + .xl\:scale-y-125 { + --transform-scale-y: 1.25; + } + + .xl\:scale-y-150 { + --transform-scale-y: 1.5; + } + + .xl\:hover\:scale-0:hover { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .xl\:hover\:scale-50:hover { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .xl\:hover\:scale-75:hover { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .xl\:hover\:scale-90:hover { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .xl\:hover\:scale-95:hover { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .xl\:hover\:scale-100:hover { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .xl\:hover\:scale-105:hover { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .xl\:hover\:scale-110:hover { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .xl\:hover\:scale-125:hover { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .xl\:hover\:scale-150:hover { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .xl\:hover\:scale-x-0:hover { + --transform-scale-x: 0; + } + + .xl\:hover\:scale-x-50:hover { + --transform-scale-x: .5; + } + + .xl\:hover\:scale-x-75:hover { + --transform-scale-x: .75; + } + + .xl\:hover\:scale-x-90:hover { + --transform-scale-x: .9; + } + + .xl\:hover\:scale-x-95:hover { + --transform-scale-x: .95; + } + + .xl\:hover\:scale-x-100:hover { + --transform-scale-x: 1; + } + + .xl\:hover\:scale-x-105:hover { + --transform-scale-x: 1.05; + } + + .xl\:hover\:scale-x-110:hover { + --transform-scale-x: 1.1; + } + + .xl\:hover\:scale-x-125:hover { + --transform-scale-x: 1.25; + } + + .xl\:hover\:scale-x-150:hover { + --transform-scale-x: 1.5; + } + + .xl\:hover\:scale-y-0:hover { + --transform-scale-y: 0; + } + + .xl\:hover\:scale-y-50:hover { + --transform-scale-y: .5; + } + + .xl\:hover\:scale-y-75:hover { + --transform-scale-y: .75; + } + + .xl\:hover\:scale-y-90:hover { + --transform-scale-y: .9; + } + + .xl\:hover\:scale-y-95:hover { + --transform-scale-y: .95; + } + + .xl\:hover\:scale-y-100:hover { + --transform-scale-y: 1; + } + + .xl\:hover\:scale-y-105:hover { + --transform-scale-y: 1.05; + } + + .xl\:hover\:scale-y-110:hover { + --transform-scale-y: 1.1; + } + + .xl\:hover\:scale-y-125:hover { + --transform-scale-y: 1.25; + } + + .xl\:hover\:scale-y-150:hover { + --transform-scale-y: 1.5; + } + + .xl\:focus\:scale-0:focus { + --transform-scale-x: 0; + --transform-scale-y: 0; + } + + .xl\:focus\:scale-50:focus { + --transform-scale-x: .5; + --transform-scale-y: .5; + } + + .xl\:focus\:scale-75:focus { + --transform-scale-x: .75; + --transform-scale-y: .75; + } + + .xl\:focus\:scale-90:focus { + --transform-scale-x: .9; + --transform-scale-y: .9; + } + + .xl\:focus\:scale-95:focus { + --transform-scale-x: .95; + --transform-scale-y: .95; + } + + .xl\:focus\:scale-100:focus { + --transform-scale-x: 1; + --transform-scale-y: 1; + } + + .xl\:focus\:scale-105:focus { + --transform-scale-x: 1.05; + --transform-scale-y: 1.05; + } + + .xl\:focus\:scale-110:focus { + --transform-scale-x: 1.1; + --transform-scale-y: 1.1; + } + + .xl\:focus\:scale-125:focus { + --transform-scale-x: 1.25; + --transform-scale-y: 1.25; + } + + .xl\:focus\:scale-150:focus { + --transform-scale-x: 1.5; + --transform-scale-y: 1.5; + } + + .xl\:focus\:scale-x-0:focus { + --transform-scale-x: 0; + } + + .xl\:focus\:scale-x-50:focus { + --transform-scale-x: .5; + } + + .xl\:focus\:scale-x-75:focus { + --transform-scale-x: .75; + } + + .xl\:focus\:scale-x-90:focus { + --transform-scale-x: .9; + } + + .xl\:focus\:scale-x-95:focus { + --transform-scale-x: .95; + } + + .xl\:focus\:scale-x-100:focus { + --transform-scale-x: 1; + } + + .xl\:focus\:scale-x-105:focus { + --transform-scale-x: 1.05; + } + + .xl\:focus\:scale-x-110:focus { + --transform-scale-x: 1.1; + } + + .xl\:focus\:scale-x-125:focus { + --transform-scale-x: 1.25; + } + + .xl\:focus\:scale-x-150:focus { + --transform-scale-x: 1.5; + } + + .xl\:focus\:scale-y-0:focus { + --transform-scale-y: 0; + } + + .xl\:focus\:scale-y-50:focus { + --transform-scale-y: .5; + } + + .xl\:focus\:scale-y-75:focus { + --transform-scale-y: .75; + } + + .xl\:focus\:scale-y-90:focus { + --transform-scale-y: .9; + } + + .xl\:focus\:scale-y-95:focus { + --transform-scale-y: .95; + } + + .xl\:focus\:scale-y-100:focus { + --transform-scale-y: 1; + } + + .xl\:focus\:scale-y-105:focus { + --transform-scale-y: 1.05; + } + + .xl\:focus\:scale-y-110:focus { + --transform-scale-y: 1.1; + } + + .xl\:focus\:scale-y-125:focus { + --transform-scale-y: 1.25; + } + + .xl\:focus\:scale-y-150:focus { + --transform-scale-y: 1.5; + } + + .xl\:rotate-0 { + --transform-rotate: 0; + } + + .xl\:rotate-45 { + --transform-rotate: 45deg; + } + + .xl\:rotate-90 { + --transform-rotate: 90deg; + } + + .xl\:rotate-180 { + --transform-rotate: 180deg; + } + + .xl\:-rotate-180 { + --transform-rotate: -180deg; + } + + .xl\:-rotate-90 { + --transform-rotate: -90deg; + } + + .xl\:-rotate-45 { + --transform-rotate: -45deg; + } + + .xl\:hover\:rotate-0:hover { + --transform-rotate: 0; + } + + .xl\:hover\:rotate-45:hover { + --transform-rotate: 45deg; + } + + .xl\:hover\:rotate-90:hover { + --transform-rotate: 90deg; + } + + .xl\:hover\:rotate-180:hover { + --transform-rotate: 180deg; + } + + .xl\:hover\:-rotate-180:hover { + --transform-rotate: -180deg; + } + + .xl\:hover\:-rotate-90:hover { + --transform-rotate: -90deg; + } + + .xl\:hover\:-rotate-45:hover { + --transform-rotate: -45deg; + } + + .xl\:focus\:rotate-0:focus { + --transform-rotate: 0; + } + + .xl\:focus\:rotate-45:focus { + --transform-rotate: 45deg; + } + + .xl\:focus\:rotate-90:focus { + --transform-rotate: 90deg; + } + + .xl\:focus\:rotate-180:focus { + --transform-rotate: 180deg; + } + + .xl\:focus\:-rotate-180:focus { + --transform-rotate: -180deg; + } + + .xl\:focus\:-rotate-90:focus { + --transform-rotate: -90deg; + } + + .xl\:focus\:-rotate-45:focus { + --transform-rotate: -45deg; + } + + .xl\:translate-x-0 { + --transform-translate-x: 0; + } + + .xl\:translate-x-1 { + --transform-translate-x: 0.25rem; + } + + .xl\:translate-x-2 { + --transform-translate-x: 0.5rem; + } + + .xl\:translate-x-3 { + --transform-translate-x: 0.75rem; + } + + .xl\:translate-x-4 { + --transform-translate-x: 1rem; + } + + .xl\:translate-x-5 { + --transform-translate-x: 1.25rem; + } + + .xl\:translate-x-6 { + --transform-translate-x: 1.5rem; + } + + .xl\:translate-x-8 { + --transform-translate-x: 2rem; + } + + .xl\:translate-x-10 { + --transform-translate-x: 2.5rem; + } + + .xl\:translate-x-12 { + --transform-translate-x: 3rem; + } + + .xl\:translate-x-16 { + --transform-translate-x: 4rem; + } + + .xl\:translate-x-20 { + --transform-translate-x: 5rem; + } + + .xl\:translate-x-24 { + --transform-translate-x: 6rem; + } + + .xl\:translate-x-32 { + --transform-translate-x: 8rem; + } + + .xl\:translate-x-40 { + --transform-translate-x: 10rem; + } + + .xl\:translate-x-48 { + --transform-translate-x: 12rem; + } + + .xl\:translate-x-56 { + --transform-translate-x: 14rem; + } + + .xl\:translate-x-64 { + --transform-translate-x: 16rem; + } + + .xl\:translate-x-px { + --transform-translate-x: 1px; + } + + .xl\:-translate-x-1 { + --transform-translate-x: -0.25rem; + } + + .xl\:-translate-x-2 { + --transform-translate-x: -0.5rem; + } + + .xl\:-translate-x-3 { + --transform-translate-x: -0.75rem; + } + + .xl\:-translate-x-4 { + --transform-translate-x: -1rem; + } + + .xl\:-translate-x-5 { + --transform-translate-x: -1.25rem; + } + + .xl\:-translate-x-6 { + --transform-translate-x: -1.5rem; + } + + .xl\:-translate-x-8 { + --transform-translate-x: -2rem; + } + + .xl\:-translate-x-10 { + --transform-translate-x: -2.5rem; + } + + .xl\:-translate-x-12 { + --transform-translate-x: -3rem; + } + + .xl\:-translate-x-16 { + --transform-translate-x: -4rem; + } + + .xl\:-translate-x-20 { + --transform-translate-x: -5rem; + } + + .xl\:-translate-x-24 { + --transform-translate-x: -6rem; + } + + .xl\:-translate-x-32 { + --transform-translate-x: -8rem; + } + + .xl\:-translate-x-40 { + --transform-translate-x: -10rem; + } + + .xl\:-translate-x-48 { + --transform-translate-x: -12rem; + } + + .xl\:-translate-x-56 { + --transform-translate-x: -14rem; + } + + .xl\:-translate-x-64 { + --transform-translate-x: -16rem; + } + + .xl\:-translate-x-px { + --transform-translate-x: -1px; + } + + .xl\:-translate-x-full { + --transform-translate-x: -100%; + } + + .xl\:-translate-x-1\/2 { + --transform-translate-x: -50%; + } + + .xl\:translate-x-1\/2 { + --transform-translate-x: 50%; + } + + .xl\:translate-x-full { + --transform-translate-x: 100%; + } + + .xl\:translate-y-0 { + --transform-translate-y: 0; + } + + .xl\:translate-y-1 { + --transform-translate-y: 0.25rem; + } + + .xl\:translate-y-2 { + --transform-translate-y: 0.5rem; + } + + .xl\:translate-y-3 { + --transform-translate-y: 0.75rem; + } + + .xl\:translate-y-4 { + --transform-translate-y: 1rem; + } + + .xl\:translate-y-5 { + --transform-translate-y: 1.25rem; + } + + .xl\:translate-y-6 { + --transform-translate-y: 1.5rem; + } + + .xl\:translate-y-8 { + --transform-translate-y: 2rem; + } + + .xl\:translate-y-10 { + --transform-translate-y: 2.5rem; + } + + .xl\:translate-y-12 { + --transform-translate-y: 3rem; + } + + .xl\:translate-y-16 { + --transform-translate-y: 4rem; + } + + .xl\:translate-y-20 { + --transform-translate-y: 5rem; + } + + .xl\:translate-y-24 { + --transform-translate-y: 6rem; + } + + .xl\:translate-y-32 { + --transform-translate-y: 8rem; + } + + .xl\:translate-y-40 { + --transform-translate-y: 10rem; + } + + .xl\:translate-y-48 { + --transform-translate-y: 12rem; + } + + .xl\:translate-y-56 { + --transform-translate-y: 14rem; + } + + .xl\:translate-y-64 { + --transform-translate-y: 16rem; + } + + .xl\:translate-y-px { + --transform-translate-y: 1px; + } + + .xl\:-translate-y-1 { + --transform-translate-y: -0.25rem; + } + + .xl\:-translate-y-2 { + --transform-translate-y: -0.5rem; + } + + .xl\:-translate-y-3 { + --transform-translate-y: -0.75rem; + } + + .xl\:-translate-y-4 { + --transform-translate-y: -1rem; + } + + .xl\:-translate-y-5 { + --transform-translate-y: -1.25rem; + } + + .xl\:-translate-y-6 { + --transform-translate-y: -1.5rem; + } + + .xl\:-translate-y-8 { + --transform-translate-y: -2rem; + } + + .xl\:-translate-y-10 { + --transform-translate-y: -2.5rem; + } + + .xl\:-translate-y-12 { + --transform-translate-y: -3rem; + } + + .xl\:-translate-y-16 { + --transform-translate-y: -4rem; + } + + .xl\:-translate-y-20 { + --transform-translate-y: -5rem; + } + + .xl\:-translate-y-24 { + --transform-translate-y: -6rem; + } + + .xl\:-translate-y-32 { + --transform-translate-y: -8rem; + } + + .xl\:-translate-y-40 { + --transform-translate-y: -10rem; + } + + .xl\:-translate-y-48 { + --transform-translate-y: -12rem; + } + + .xl\:-translate-y-56 { + --transform-translate-y: -14rem; + } + + .xl\:-translate-y-64 { + --transform-translate-y: -16rem; + } + + .xl\:-translate-y-px { + --transform-translate-y: -1px; + } + + .xl\:-translate-y-full { + --transform-translate-y: -100%; + } + + .xl\:-translate-y-1\/2 { + --transform-translate-y: -50%; + } + + .xl\:translate-y-1\/2 { + --transform-translate-y: 50%; + } + + .xl\:translate-y-full { + --transform-translate-y: 100%; + } + + .xl\:hover\:translate-x-0:hover { + --transform-translate-x: 0; + } + + .xl\:hover\:translate-x-1:hover { + --transform-translate-x: 0.25rem; + } + + .xl\:hover\:translate-x-2:hover { + --transform-translate-x: 0.5rem; + } + + .xl\:hover\:translate-x-3:hover { + --transform-translate-x: 0.75rem; + } + + .xl\:hover\:translate-x-4:hover { + --transform-translate-x: 1rem; + } + + .xl\:hover\:translate-x-5:hover { + --transform-translate-x: 1.25rem; + } + + .xl\:hover\:translate-x-6:hover { + --transform-translate-x: 1.5rem; + } + + .xl\:hover\:translate-x-8:hover { + --transform-translate-x: 2rem; + } + + .xl\:hover\:translate-x-10:hover { + --transform-translate-x: 2.5rem; + } + + .xl\:hover\:translate-x-12:hover { + --transform-translate-x: 3rem; + } + + .xl\:hover\:translate-x-16:hover { + --transform-translate-x: 4rem; + } + + .xl\:hover\:translate-x-20:hover { + --transform-translate-x: 5rem; + } + + .xl\:hover\:translate-x-24:hover { + --transform-translate-x: 6rem; + } + + .xl\:hover\:translate-x-32:hover { + --transform-translate-x: 8rem; + } + + .xl\:hover\:translate-x-40:hover { + --transform-translate-x: 10rem; + } + + .xl\:hover\:translate-x-48:hover { + --transform-translate-x: 12rem; + } + + .xl\:hover\:translate-x-56:hover { + --transform-translate-x: 14rem; + } + + .xl\:hover\:translate-x-64:hover { + --transform-translate-x: 16rem; + } + + .xl\:hover\:translate-x-px:hover { + --transform-translate-x: 1px; + } + + .xl\:hover\:-translate-x-1:hover { + --transform-translate-x: -0.25rem; + } + + .xl\:hover\:-translate-x-2:hover { + --transform-translate-x: -0.5rem; + } + + .xl\:hover\:-translate-x-3:hover { + --transform-translate-x: -0.75rem; + } + + .xl\:hover\:-translate-x-4:hover { + --transform-translate-x: -1rem; + } + + .xl\:hover\:-translate-x-5:hover { + --transform-translate-x: -1.25rem; + } + + .xl\:hover\:-translate-x-6:hover { + --transform-translate-x: -1.5rem; + } + + .xl\:hover\:-translate-x-8:hover { + --transform-translate-x: -2rem; + } + + .xl\:hover\:-translate-x-10:hover { + --transform-translate-x: -2.5rem; + } + + .xl\:hover\:-translate-x-12:hover { + --transform-translate-x: -3rem; + } + + .xl\:hover\:-translate-x-16:hover { + --transform-translate-x: -4rem; + } + + .xl\:hover\:-translate-x-20:hover { + --transform-translate-x: -5rem; + } + + .xl\:hover\:-translate-x-24:hover { + --transform-translate-x: -6rem; + } + + .xl\:hover\:-translate-x-32:hover { + --transform-translate-x: -8rem; + } + + .xl\:hover\:-translate-x-40:hover { + --transform-translate-x: -10rem; + } + + .xl\:hover\:-translate-x-48:hover { + --transform-translate-x: -12rem; + } + + .xl\:hover\:-translate-x-56:hover { + --transform-translate-x: -14rem; + } + + .xl\:hover\:-translate-x-64:hover { + --transform-translate-x: -16rem; + } + + .xl\:hover\:-translate-x-px:hover { + --transform-translate-x: -1px; + } + + .xl\:hover\:-translate-x-full:hover { + --transform-translate-x: -100%; + } + + .xl\:hover\:-translate-x-1\/2:hover { + --transform-translate-x: -50%; + } + + .xl\:hover\:translate-x-1\/2:hover { + --transform-translate-x: 50%; + } + + .xl\:hover\:translate-x-full:hover { + --transform-translate-x: 100%; + } + + .xl\:hover\:translate-y-0:hover { + --transform-translate-y: 0; + } + + .xl\:hover\:translate-y-1:hover { + --transform-translate-y: 0.25rem; + } + + .xl\:hover\:translate-y-2:hover { + --transform-translate-y: 0.5rem; + } + + .xl\:hover\:translate-y-3:hover { + --transform-translate-y: 0.75rem; + } + + .xl\:hover\:translate-y-4:hover { + --transform-translate-y: 1rem; + } + + .xl\:hover\:translate-y-5:hover { + --transform-translate-y: 1.25rem; + } + + .xl\:hover\:translate-y-6:hover { + --transform-translate-y: 1.5rem; + } + + .xl\:hover\:translate-y-8:hover { + --transform-translate-y: 2rem; + } + + .xl\:hover\:translate-y-10:hover { + --transform-translate-y: 2.5rem; + } + + .xl\:hover\:translate-y-12:hover { + --transform-translate-y: 3rem; + } + + .xl\:hover\:translate-y-16:hover { + --transform-translate-y: 4rem; + } + + .xl\:hover\:translate-y-20:hover { + --transform-translate-y: 5rem; + } + + .xl\:hover\:translate-y-24:hover { + --transform-translate-y: 6rem; + } + + .xl\:hover\:translate-y-32:hover { + --transform-translate-y: 8rem; + } + + .xl\:hover\:translate-y-40:hover { + --transform-translate-y: 10rem; + } + + .xl\:hover\:translate-y-48:hover { + --transform-translate-y: 12rem; + } + + .xl\:hover\:translate-y-56:hover { + --transform-translate-y: 14rem; + } + + .xl\:hover\:translate-y-64:hover { + --transform-translate-y: 16rem; + } + + .xl\:hover\:translate-y-px:hover { + --transform-translate-y: 1px; + } + + .xl\:hover\:-translate-y-1:hover { + --transform-translate-y: -0.25rem; + } + + .xl\:hover\:-translate-y-2:hover { + --transform-translate-y: -0.5rem; + } + + .xl\:hover\:-translate-y-3:hover { + --transform-translate-y: -0.75rem; + } + + .xl\:hover\:-translate-y-4:hover { + --transform-translate-y: -1rem; + } + + .xl\:hover\:-translate-y-5:hover { + --transform-translate-y: -1.25rem; + } + + .xl\:hover\:-translate-y-6:hover { + --transform-translate-y: -1.5rem; + } + + .xl\:hover\:-translate-y-8:hover { + --transform-translate-y: -2rem; + } + + .xl\:hover\:-translate-y-10:hover { + --transform-translate-y: -2.5rem; + } + + .xl\:hover\:-translate-y-12:hover { + --transform-translate-y: -3rem; + } + + .xl\:hover\:-translate-y-16:hover { + --transform-translate-y: -4rem; + } + + .xl\:hover\:-translate-y-20:hover { + --transform-translate-y: -5rem; + } + + .xl\:hover\:-translate-y-24:hover { + --transform-translate-y: -6rem; + } + + .xl\:hover\:-translate-y-32:hover { + --transform-translate-y: -8rem; + } + + .xl\:hover\:-translate-y-40:hover { + --transform-translate-y: -10rem; + } + + .xl\:hover\:-translate-y-48:hover { + --transform-translate-y: -12rem; + } + + .xl\:hover\:-translate-y-56:hover { + --transform-translate-y: -14rem; + } + + .xl\:hover\:-translate-y-64:hover { + --transform-translate-y: -16rem; + } + + .xl\:hover\:-translate-y-px:hover { + --transform-translate-y: -1px; + } + + .xl\:hover\:-translate-y-full:hover { + --transform-translate-y: -100%; + } + + .xl\:hover\:-translate-y-1\/2:hover { + --transform-translate-y: -50%; + } + + .xl\:hover\:translate-y-1\/2:hover { + --transform-translate-y: 50%; + } + + .xl\:hover\:translate-y-full:hover { + --transform-translate-y: 100%; + } + + .xl\:focus\:translate-x-0:focus { + --transform-translate-x: 0; + } + + .xl\:focus\:translate-x-1:focus { + --transform-translate-x: 0.25rem; + } + + .xl\:focus\:translate-x-2:focus { + --transform-translate-x: 0.5rem; + } + + .xl\:focus\:translate-x-3:focus { + --transform-translate-x: 0.75rem; + } + + .xl\:focus\:translate-x-4:focus { + --transform-translate-x: 1rem; + } + + .xl\:focus\:translate-x-5:focus { + --transform-translate-x: 1.25rem; + } + + .xl\:focus\:translate-x-6:focus { + --transform-translate-x: 1.5rem; + } + + .xl\:focus\:translate-x-8:focus { + --transform-translate-x: 2rem; + } + + .xl\:focus\:translate-x-10:focus { + --transform-translate-x: 2.5rem; + } + + .xl\:focus\:translate-x-12:focus { + --transform-translate-x: 3rem; + } + + .xl\:focus\:translate-x-16:focus { + --transform-translate-x: 4rem; + } + + .xl\:focus\:translate-x-20:focus { + --transform-translate-x: 5rem; + } + + .xl\:focus\:translate-x-24:focus { + --transform-translate-x: 6rem; + } + + .xl\:focus\:translate-x-32:focus { + --transform-translate-x: 8rem; + } + + .xl\:focus\:translate-x-40:focus { + --transform-translate-x: 10rem; + } + + .xl\:focus\:translate-x-48:focus { + --transform-translate-x: 12rem; + } + + .xl\:focus\:translate-x-56:focus { + --transform-translate-x: 14rem; + } + + .xl\:focus\:translate-x-64:focus { + --transform-translate-x: 16rem; + } + + .xl\:focus\:translate-x-px:focus { + --transform-translate-x: 1px; + } + + .xl\:focus\:-translate-x-1:focus { + --transform-translate-x: -0.25rem; + } + + .xl\:focus\:-translate-x-2:focus { + --transform-translate-x: -0.5rem; + } + + .xl\:focus\:-translate-x-3:focus { + --transform-translate-x: -0.75rem; + } + + .xl\:focus\:-translate-x-4:focus { + --transform-translate-x: -1rem; + } + + .xl\:focus\:-translate-x-5:focus { + --transform-translate-x: -1.25rem; + } + + .xl\:focus\:-translate-x-6:focus { + --transform-translate-x: -1.5rem; + } + + .xl\:focus\:-translate-x-8:focus { + --transform-translate-x: -2rem; + } + + .xl\:focus\:-translate-x-10:focus { + --transform-translate-x: -2.5rem; + } + + .xl\:focus\:-translate-x-12:focus { + --transform-translate-x: -3rem; + } + + .xl\:focus\:-translate-x-16:focus { + --transform-translate-x: -4rem; + } + + .xl\:focus\:-translate-x-20:focus { + --transform-translate-x: -5rem; + } + + .xl\:focus\:-translate-x-24:focus { + --transform-translate-x: -6rem; + } + + .xl\:focus\:-translate-x-32:focus { + --transform-translate-x: -8rem; + } + + .xl\:focus\:-translate-x-40:focus { + --transform-translate-x: -10rem; + } + + .xl\:focus\:-translate-x-48:focus { + --transform-translate-x: -12rem; + } + + .xl\:focus\:-translate-x-56:focus { + --transform-translate-x: -14rem; + } + + .xl\:focus\:-translate-x-64:focus { + --transform-translate-x: -16rem; + } + + .xl\:focus\:-translate-x-px:focus { + --transform-translate-x: -1px; + } + + .xl\:focus\:-translate-x-full:focus { + --transform-translate-x: -100%; + } + + .xl\:focus\:-translate-x-1\/2:focus { + --transform-translate-x: -50%; + } + + .xl\:focus\:translate-x-1\/2:focus { + --transform-translate-x: 50%; + } + + .xl\:focus\:translate-x-full:focus { + --transform-translate-x: 100%; + } + + .xl\:focus\:translate-y-0:focus { + --transform-translate-y: 0; + } + + .xl\:focus\:translate-y-1:focus { + --transform-translate-y: 0.25rem; + } + + .xl\:focus\:translate-y-2:focus { + --transform-translate-y: 0.5rem; + } + + .xl\:focus\:translate-y-3:focus { + --transform-translate-y: 0.75rem; + } + + .xl\:focus\:translate-y-4:focus { + --transform-translate-y: 1rem; + } + + .xl\:focus\:translate-y-5:focus { + --transform-translate-y: 1.25rem; + } + + .xl\:focus\:translate-y-6:focus { + --transform-translate-y: 1.5rem; + } + + .xl\:focus\:translate-y-8:focus { + --transform-translate-y: 2rem; + } + + .xl\:focus\:translate-y-10:focus { + --transform-translate-y: 2.5rem; + } + + .xl\:focus\:translate-y-12:focus { + --transform-translate-y: 3rem; + } + + .xl\:focus\:translate-y-16:focus { + --transform-translate-y: 4rem; + } + + .xl\:focus\:translate-y-20:focus { + --transform-translate-y: 5rem; + } + + .xl\:focus\:translate-y-24:focus { + --transform-translate-y: 6rem; + } + + .xl\:focus\:translate-y-32:focus { + --transform-translate-y: 8rem; + } + + .xl\:focus\:translate-y-40:focus { + --transform-translate-y: 10rem; + } + + .xl\:focus\:translate-y-48:focus { + --transform-translate-y: 12rem; + } + + .xl\:focus\:translate-y-56:focus { + --transform-translate-y: 14rem; + } + + .xl\:focus\:translate-y-64:focus { + --transform-translate-y: 16rem; + } + + .xl\:focus\:translate-y-px:focus { + --transform-translate-y: 1px; + } + + .xl\:focus\:-translate-y-1:focus { + --transform-translate-y: -0.25rem; + } + + .xl\:focus\:-translate-y-2:focus { + --transform-translate-y: -0.5rem; + } + + .xl\:focus\:-translate-y-3:focus { + --transform-translate-y: -0.75rem; + } + + .xl\:focus\:-translate-y-4:focus { + --transform-translate-y: -1rem; + } + + .xl\:focus\:-translate-y-5:focus { + --transform-translate-y: -1.25rem; + } + + .xl\:focus\:-translate-y-6:focus { + --transform-translate-y: -1.5rem; + } + + .xl\:focus\:-translate-y-8:focus { + --transform-translate-y: -2rem; + } + + .xl\:focus\:-translate-y-10:focus { + --transform-translate-y: -2.5rem; + } + + .xl\:focus\:-translate-y-12:focus { + --transform-translate-y: -3rem; + } + + .xl\:focus\:-translate-y-16:focus { + --transform-translate-y: -4rem; + } + + .xl\:focus\:-translate-y-20:focus { + --transform-translate-y: -5rem; + } + + .xl\:focus\:-translate-y-24:focus { + --transform-translate-y: -6rem; + } + + .xl\:focus\:-translate-y-32:focus { + --transform-translate-y: -8rem; + } + + .xl\:focus\:-translate-y-40:focus { + --transform-translate-y: -10rem; + } + + .xl\:focus\:-translate-y-48:focus { + --transform-translate-y: -12rem; + } + + .xl\:focus\:-translate-y-56:focus { + --transform-translate-y: -14rem; + } + + .xl\:focus\:-translate-y-64:focus { + --transform-translate-y: -16rem; + } + + .xl\:focus\:-translate-y-px:focus { + --transform-translate-y: -1px; + } + + .xl\:focus\:-translate-y-full:focus { + --transform-translate-y: -100%; + } + + .xl\:focus\:-translate-y-1\/2:focus { + --transform-translate-y: -50%; + } + + .xl\:focus\:translate-y-1\/2:focus { + --transform-translate-y: 50%; + } + + .xl\:focus\:translate-y-full:focus { + --transform-translate-y: 100%; + } + + .xl\:skew-x-0 { + --transform-skew-x: 0; + } + + .xl\:skew-x-3 { + --transform-skew-x: 3deg; + } + + .xl\:skew-x-6 { + --transform-skew-x: 6deg; + } + + .xl\:skew-x-12 { + --transform-skew-x: 12deg; + } + + .xl\:-skew-x-12 { + --transform-skew-x: -12deg; + } + + .xl\:-skew-x-6 { + --transform-skew-x: -6deg; + } + + .xl\:-skew-x-3 { + --transform-skew-x: -3deg; + } + + .xl\:skew-y-0 { + --transform-skew-y: 0; + } + + .xl\:skew-y-3 { + --transform-skew-y: 3deg; + } + + .xl\:skew-y-6 { + --transform-skew-y: 6deg; + } + + .xl\:skew-y-12 { + --transform-skew-y: 12deg; + } + + .xl\:-skew-y-12 { + --transform-skew-y: -12deg; + } + + .xl\:-skew-y-6 { + --transform-skew-y: -6deg; + } + + .xl\:-skew-y-3 { + --transform-skew-y: -3deg; + } + + .xl\:hover\:skew-x-0:hover { + --transform-skew-x: 0; + } + + .xl\:hover\:skew-x-3:hover { + --transform-skew-x: 3deg; + } + + .xl\:hover\:skew-x-6:hover { + --transform-skew-x: 6deg; + } + + .xl\:hover\:skew-x-12:hover { + --transform-skew-x: 12deg; + } + + .xl\:hover\:-skew-x-12:hover { + --transform-skew-x: -12deg; + } + + .xl\:hover\:-skew-x-6:hover { + --transform-skew-x: -6deg; + } + + .xl\:hover\:-skew-x-3:hover { + --transform-skew-x: -3deg; + } + + .xl\:hover\:skew-y-0:hover { + --transform-skew-y: 0; + } + + .xl\:hover\:skew-y-3:hover { + --transform-skew-y: 3deg; + } + + .xl\:hover\:skew-y-6:hover { + --transform-skew-y: 6deg; + } + + .xl\:hover\:skew-y-12:hover { + --transform-skew-y: 12deg; + } + + .xl\:hover\:-skew-y-12:hover { + --transform-skew-y: -12deg; + } + + .xl\:hover\:-skew-y-6:hover { + --transform-skew-y: -6deg; + } + + .xl\:hover\:-skew-y-3:hover { + --transform-skew-y: -3deg; + } + + .xl\:focus\:skew-x-0:focus { + --transform-skew-x: 0; + } + + .xl\:focus\:skew-x-3:focus { + --transform-skew-x: 3deg; + } + + .xl\:focus\:skew-x-6:focus { + --transform-skew-x: 6deg; + } + + .xl\:focus\:skew-x-12:focus { + --transform-skew-x: 12deg; + } + + .xl\:focus\:-skew-x-12:focus { + --transform-skew-x: -12deg; + } + + .xl\:focus\:-skew-x-6:focus { + --transform-skew-x: -6deg; + } + + .xl\:focus\:-skew-x-3:focus { + --transform-skew-x: -3deg; + } + + .xl\:focus\:skew-y-0:focus { + --transform-skew-y: 0; + } + + .xl\:focus\:skew-y-3:focus { + --transform-skew-y: 3deg; + } + + .xl\:focus\:skew-y-6:focus { + --transform-skew-y: 6deg; + } + + .xl\:focus\:skew-y-12:focus { + --transform-skew-y: 12deg; + } + + .xl\:focus\:-skew-y-12:focus { + --transform-skew-y: -12deg; + } + + .xl\:focus\:-skew-y-6:focus { + --transform-skew-y: -6deg; + } + + .xl\:focus\:-skew-y-3:focus { + --transform-skew-y: -3deg; + } + + .xl\:transition-none { + transition-property: none; + } + + .xl\:transition-all { + transition-property: all; + } + + .xl\:transition { + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; + } + + .xl\:transition-colors { + transition-property: background-color, border-color, color, fill, stroke; + } + + .xl\:transition-opacity { + transition-property: opacity; + } + + .xl\:transition-shadow { + transition-property: box-shadow; + } + + .xl\:transition-transform { + transition-property: transform; + } + + .xl\:ease-linear { + transition-timing-function: linear; + } + + .xl\:ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); + } + + .xl\:ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + + .xl\:ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .xl\:duration-75 { + transition-duration: 75ms; + } + + .xl\:duration-100 { + transition-duration: 100ms; + } + + .xl\:duration-150 { + transition-duration: 150ms; + } + + .xl\:duration-200 { + transition-duration: 200ms; + } + + .xl\:duration-300 { + transition-duration: 300ms; + } + + .xl\:duration-500 { + transition-duration: 500ms; + } + + .xl\:duration-700 { + transition-duration: 700ms; + } + + .xl\:duration-1000 { + transition-duration: 1000ms; + } + + .xl\:delay-75 { + transition-delay: 75ms; + } + + .xl\:delay-100 { + transition-delay: 100ms; + } + + .xl\:delay-150 { + transition-delay: 150ms; + } + + .xl\:delay-200 { + transition-delay: 200ms; + } + + .xl\:delay-300 { + transition-delay: 300ms; + } + + .xl\:delay-500 { + transition-delay: 500ms; + } + + .xl\:delay-700 { + transition-delay: 700ms; + } + + .xl\:delay-1000 { + transition-delay: 1000ms; + } + + .xl\:animate-none { + -webkit-animation: none; + animation: none; + } + + .xl\:animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + } + + .xl\:animate-ping { + -webkit-animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + } + + .xl\:animate-pulse { + -webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + .xl\:animate-bounce { + -webkit-animation: bounce 1s infinite; + animation: bounce 1s infinite; + } +} diff --git a/users/wpcarro/website/habit-screens/registry.dat b/users/wpcarro/website/habit-screens/registry.dat new file mode 100644 index 000000000000..d2671b2cf17a --- /dev/null +++ b/users/wpcarro/website/habit-screens/registry.dat Binary files differdiff --git a/users/wpcarro/website/habit-screens/shell.nix b/users/wpcarro/website/habit-screens/shell.nix new file mode 100644 index 000000000000..00bb4b0b3edc --- /dev/null +++ b/users/wpcarro/website/habit-screens/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs.elmPackages; [ + elm + elm-format + elm-live + ]; +} diff --git a/users/wpcarro/website/habit-screens/src/Habits.elm b/users/wpcarro/website/habit-screens/src/Habits.elm new file mode 100644 index 000000000000..691adc939464 --- /dev/null +++ b/users/wpcarro/website/habit-screens/src/Habits.elm @@ -0,0 +1,463 @@ +module Habits exposing (render) + +import Browser +import Date exposing (Date) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Set exposing (Set) +import State exposing (HabitType(..)) +import Time exposing (Weekday(..)) +import UI +import Utils exposing (Strategy(..)) + + +morning : List State.Habit +morning = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.Morning + , minutesDuration = duration + } + ) + [ ( 1, "Make bed" ) + , ( 2, "Brush teeth" ) + , ( 30, "Run (15 minutes)" ) + , ( 10, "Shower" ) + , ( 10, "Meditate" ) + ] + + +evening : List State.Habit +evening = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.Evening + , minutesDuration = duration + } + ) + [ ( 30, "Read" ) + , ( 1, "Record in habit Journal" ) + ] + + +monday : List ( Int, String ) +monday = + [ ( 90, "Bikram Yoga @ 17:00" ) + ] + + +tuesday : List ( Int, String ) +tuesday = + [ ( 90, "Bikram Yoga @ 18:00" ) + ] + + +wednesday : List ( Int, String ) +wednesday = + [ ( 5, "Shave" ) + , ( 90, "Bikram Yoga @ 17:00" ) + ] + + +thursday : List ( Int, String ) +thursday = + [] + + +friday : List ( Int, String ) +friday = + [ ( 60, "Bikram Yoga @ 17:00" ) + , ( 3, "Take-out trash" ) + , ( 60, "Shop for groceries" ) + ] + + +saturday : List ( Int, String ) +saturday = + [ ( 60, "Warm Yin Yoga @ 15:00" ) + ] + + +sunday : List ( Int, String ) +sunday = + [ ( 1, "Shampoo" ) + , ( 5, "Shave" ) + , ( 1, "Trim nails" ) + , ( 1, "Combine trash cans" ) + , ( 10, "Mop tile and wood floors" ) + , ( 10, "Laundry" ) + , ( 5, "Vacuum bedroom" ) + , ( 5, "Clean desk" ) + ] + + +payday : List State.Habit +payday = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.Payday + , minutesDuration = duration + } + ) + [ ( 1, "Ensure \"Emergency\" fund has a balance of 1000 GBP" ) + , ( 1, "Open \"finances_2020\" Google Sheet" ) + , ( 1, "Settle up with Mimi on TransferWise" ) + , ( 1, "Adjust GBP:USD exchange rate" ) + , ( 1, "Adjust \"Stocks (after tax)\" to reflect amount Google sent" ) + , ( 1, "Add remaining cash to \"Carryover (cash)\"" ) + , ( 1, "Adjust \"Paycheck\" to reflect amount Google sent" ) + , ( 5, "In the \"International Xfer\" table, send \"Xfer amount\" from Monzo to USAA" ) + , ( 10, "Go to an ATM and extract the amount in \"ATM withdrawal\"" ) + , ( 0, "Await the TransferWise transaction to complete and pay MyFedLoan in USD" ) + ] + + +firstOfTheMonth : List State.Habit +firstOfTheMonth = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.FirstOfTheMonth + , minutesDuration = duration + } + ) + [ ( 10, "Create habit template in journal" ) + , ( 30, "Assess previous month's performance" ) + , ( 5, "Register for Bikram Yoga classes" ) + ] + + +firstOfTheYear : List State.Habit +firstOfTheYear = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.FirstOfTheYear + , minutesDuration = duration + } + ) + [ ( 60, "Write a post mortem for the previous year" ) + ] + + +habitTypes : + { includeMorning : Bool + , includeEvening : Bool + , date : Date + } + -> List State.HabitType +habitTypes { includeMorning, includeEvening, date } = + let + habitTypePredicates : List ( State.HabitType, Date -> Bool ) + habitTypePredicates = + [ ( Morning, \_ -> includeMorning ) + , ( DayOfWeek, \_ -> True ) + , ( Payday, \x -> Date.day x == 25 ) + , ( FirstOfTheMonth, \x -> Date.day x == 1 ) + , ( FirstOfTheYear, \x -> Date.day x == 1 && Date.monthNumber x == 1 ) + , ( Evening, \_ -> includeEvening ) + ] + in + habitTypePredicates + |> List.filter (\( _, predicate ) -> predicate date) + |> List.map (\( habitType, _ ) -> habitType) + + +habitsFor : State.HabitType -> Weekday -> List State.Habit +habitsFor habitType weekday = + case habitType of + Morning -> + morning + + Evening -> + evening + + DayOfWeek -> + let + toHabit : List ( Int, String ) -> List State.Habit + toHabit = + List.map + (\( duration, x ) -> + { label = x + , habitType = State.DayOfWeek + , minutesDuration = duration + } + ) + in + case weekday of + Mon -> + toHabit monday + + Tue -> + toHabit tuesday + + Wed -> + toHabit wednesday + + Thu -> + toHabit thursday + + Fri -> + toHabit friday + + Sat -> + toHabit saturday + + Sun -> + toHabit sunday + + Payday -> + payday + + FirstOfTheMonth -> + firstOfTheMonth + + FirstOfTheYear -> + firstOfTheYear + + +weekdayLabelFor : Weekday -> State.WeekdayLabel +weekdayLabelFor weekday = + case weekday of + Mon -> + "Monday" + + Tue -> + "Tuesday" + + Wed -> + "Wednesday" + + Thu -> + "Thursday" + + Fri -> + "Friday" + + Sat -> + "Saturday" + + Sun -> + "Sunday" + + +timeRemaining : State.WeekdayLabel -> State.CompletedHabits -> List State.Habit -> Int +timeRemaining weekdayLabel completed habits = + habits + |> List.indexedMap + (\i { label, minutesDuration } -> + if Set.member ( weekdayLabel, label ) completed then + 0 + + else + minutesDuration + ) + |> List.sum + + +render : State.Model -> Html State.Msg +render { today, visibleDayOfWeek, completed, includeMorning, includeEvening } = + case ( today, visibleDayOfWeek ) of + ( Just todaysDate, Just visibleWeekday ) -> + let + todaysWeekday : Weekday + todaysWeekday = + Date.weekday todaysDate + + habits : List State.Habit + habits = + habitTypes + { includeMorning = includeMorning + , includeEvening = includeEvening + , date = todaysDate + } + |> List.map (\habitType -> habitsFor habitType todaysWeekday) + |> List.concat + in + div + [ Utils.class + [ Always "max-w-xl mx-auto py-6 px-6" + , When (todaysWeekday /= visibleWeekday) "pt-20" + ] + ] + [ header [] + [ if todaysWeekday /= visibleWeekday then + div [ class "text-center w-full bg-blue-600 text-white fixed top-0 left-0 px-3 py-4" ] + [ p [ class "py-2 inline pr-5" ] + [ text "As you are not viewing today's habits, the UI is in read-only mode" ] + , UI.button + [ class "bg-blue-200 px-4 py-2 rounded text-blue-600 text-xs font-bold" + , onClick State.ViewToday + ] + [ text "View Today's Habits" ] + ] + + else + text "" + , div [ class "flex center" ] + [ UI.button + [ class "w-1/4 text-gray-500" + , onClick State.ViewPrevious + ] + [ text "โน previous" ] + , h1 [ class "font-bold text-blue-500 text-3xl text-center w-full" ] + [ text (weekdayLabelFor visibleWeekday) ] + , UI.button + [ class "w-1/4 text-gray-500" + , onClick State.ViewNext + ] + [ text "next โบ" ] + ] + ] + , if todaysWeekday == visibleWeekday then + p [ class "text-center pt-1 pb-4" ] + [ let + t : Int + t = + timeRemaining (weekdayLabelFor todaysWeekday) completed habits + in + if t == 0 then + text "Nothing to do!" + + else + text + ((habits + |> timeRemaining (weekdayLabelFor todaysWeekday) completed + |> String.fromInt + ) + ++ " minutes remaining" + ) + ] + + else + text "" + , if todaysWeekday == visibleWeekday then + div [] + [ UI.button + [ onClick + (if Set.size completed == 0 then + State.DoNothing + + else + State.ClearAll + ) + , Utils.class + [ Always "ml-10 px-3" + , If (Set.size completed == 0) + "text-gray-500 cursor-not-allowed" + "text-red-500 underline cursor-pointer" + ] + ] + [ let + numCompleted : Int + numCompleted = + habits + |> List.indexedMap (\i { label } -> ( i, label )) + |> List.filter + (\( i, label ) -> + Set.member + ( weekdayLabelFor todaysWeekday, label ) + completed + ) + |> List.length + in + if numCompleted == 0 then + text "Clear" + + else + text ("Clear (" ++ String.fromInt numCompleted ++ ")") + ] + , UI.button + [ onClick State.ToggleMorning + , Utils.class + [ Always "px-3 underline" + , If includeMorning + "text-gray-600" + "text-blue-600" + ] + ] + [ text + (if includeMorning then + "Hide Morning" + + else + "Show Morning" + ) + ] + , UI.button + [ Utils.class + [ Always "px-3 underline" + , If includeEvening + "text-gray-600" + "text-blue-600" + ] + , onClick State.ToggleEvening + ] + [ text + (if includeEvening then + "Hide Evening" + + else + "Show Evening" + ) + ] + ] + + else + text "" + , ul [ class "pb-10" ] + (habits + |> List.indexedMap + (\i { label, minutesDuration } -> + let + isCompleted : Bool + isCompleted = + Set.member ( weekdayLabelFor todaysWeekday, label ) completed + in + li [ class "text-xl list-disc ml-6" ] + [ if todaysWeekday == visibleWeekday then + UI.button + [ class "py-5 px-3" + , onClick + (State.ToggleHabit + (weekdayLabelFor todaysWeekday) + label + ) + ] + [ span + [ Utils.class + [ Always "text-white pt-1 px-2 rounded" + , If isCompleted "bg-gray-400" "bg-blue-500" + ] + ] + [ text (String.fromInt minutesDuration ++ " mins") ] + , p + [ Utils.class + [ Always "inline pl-3" + , When isCompleted "line-through text-gray-400" + ] + ] + [ text label ] + ] + + else + UI.button + [ class "py-5 px-3 cursor-not-allowed" + , onClick State.DoNothing + ] + [ text label ] + ] + ) + ) + , footer [ class "bg-white text-sm text-center text-gray-500 fixed bottom-0 left-0 w-full py-4" ] + [ p [] [ text "This app is brought to you by William Carroll." ] + , p [] [ text "Client: Elm; Server: n/a" ] + ] + ] + + ( _, _ ) -> + p [] [ text "Unable to display habits because we do not know what day of the week it is." ] diff --git a/users/wpcarro/website/habit-screens/src/Main.elm b/users/wpcarro/website/habit-screens/src/Main.elm new file mode 100644 index 000000000000..2ddedb913357 --- /dev/null +++ b/users/wpcarro/website/habit-screens/src/Main.elm @@ -0,0 +1,29 @@ +module Main exposing (main) + +import Browser +import Habits +import Html exposing (..) +import State +import Time + + +subscriptions : State.Model -> Sub State.Msg +subscriptions model = + -- once per minute + Time.every (1000 * 60) (\_ -> State.MaybeAdjustWeekday) + + +view : State.Model -> Html State.Msg +view model = + case model.view of + State.Habits -> + Habits.render model + + +main = + Browser.element + { init = \() -> State.init + , subscriptions = subscriptions + , update = State.update + , view = view + } diff --git a/users/wpcarro/website/habit-screens/src/State.elm b/users/wpcarro/website/habit-screens/src/State.elm new file mode 100644 index 000000000000..ea00a013513e --- /dev/null +++ b/users/wpcarro/website/habit-screens/src/State.elm @@ -0,0 +1,195 @@ +module State exposing (..) + +import Date exposing (Date) +import Set exposing (Set) +import Task +import Time exposing (Weekday(..)) + + +type alias WeekdayLabel = + String + + +type alias HabitLabel = + String + + +type Msg + = DoNothing + | SetView View + | ReceiveDate Date + | ToggleHabit WeekdayLabel HabitLabel + | MaybeAdjustWeekday + | ViewToday + | ViewPrevious + | ViewNext + | ClearAll + | ToggleMorning + | ToggleEvening + + +type View + = Habits + + +type HabitType + = Morning + | Evening + | DayOfWeek + | Payday + | FirstOfTheMonth + | FirstOfTheYear + + +type alias Habit = + { label : HabitLabel + , habitType : HabitType + , minutesDuration : Int + } + + +type alias CompletedHabits = + Set ( WeekdayLabel, HabitLabel ) + + +type alias Model = + { isLoading : Bool + , view : View + , today : Maybe Date + , completed : CompletedHabits + , visibleDayOfWeek : Maybe Weekday + , includeMorning : Bool + , includeEvening : Bool + } + + +previousDay : Weekday -> Weekday +previousDay weekday = + case weekday of + Mon -> + Sun + + Tue -> + Mon + + Wed -> + Tue + + Thu -> + Wed + + Fri -> + Thu + + Sat -> + Fri + + Sun -> + Sat + + +nextDay : Weekday -> Weekday +nextDay weekday = + case weekday of + Mon -> + Tue + + Tue -> + Wed + + Wed -> + Thu + + Thu -> + Fri + + Fri -> + Sat + + Sat -> + Sun + + Sun -> + Mon + + +{-| The initial state for the application. +-} +init : ( Model, Cmd Msg ) +init = + ( { isLoading = False + , view = Habits + , today = Nothing + , completed = Set.empty + , visibleDayOfWeek = Nothing + , includeMorning = True + , includeEvening = True + } + , Date.today |> Task.perform ReceiveDate + ) + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg ({ today, visibleDayOfWeek, completed } as model) = + case msg of + DoNothing -> + ( model, Cmd.none ) + + SetView x -> + ( { model + | view = x + , isLoading = True + } + , Cmd.none + ) + + ReceiveDate x -> + ( { model + | today = Just x + , visibleDayOfWeek = Just (Date.weekday x) + } + , Cmd.none + ) + + ToggleHabit weekdayLabel habitLabel -> + ( { model + | completed = + if Set.member ( weekdayLabel, habitLabel ) completed then + Set.remove ( weekdayLabel, habitLabel ) completed + + else + Set.insert ( weekdayLabel, habitLabel ) completed + } + , Cmd.none + ) + + MaybeAdjustWeekday -> + ( model, Date.today |> Task.perform ReceiveDate ) + + ViewToday -> + ( { model | visibleDayOfWeek = today |> Maybe.map Date.weekday }, Cmd.none ) + + ViewPrevious -> + ( { model + | visibleDayOfWeek = visibleDayOfWeek |> Maybe.map previousDay + } + , Cmd.none + ) + + ViewNext -> + ( { model + | visibleDayOfWeek = visibleDayOfWeek |> Maybe.map nextDay + } + , Cmd.none + ) + + ClearAll -> + ( { model | completed = Set.empty }, Cmd.none ) + + ToggleMorning -> + ( { model | includeMorning = not model.includeMorning }, Cmd.none ) + + ToggleEvening -> + ( { model | includeEvening = not model.includeEvening }, Cmd.none ) diff --git a/users/wpcarro/website/habit-screens/src/UI.elm b/users/wpcarro/website/habit-screens/src/UI.elm new file mode 100644 index 000000000000..5b5426913570 --- /dev/null +++ b/users/wpcarro/website/habit-screens/src/UI.elm @@ -0,0 +1,9 @@ +module UI exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (..) + + +button : List (Attribute msg) -> List (Html msg) -> Html msg +button attrs children = + Html.button ([ class "focus:outline-none" ] ++ attrs) children diff --git a/users/wpcarro/website/habit-screens/src/Utils.elm b/users/wpcarro/website/habit-screens/src/Utils.elm new file mode 100644 index 000000000000..23b13c224c68 --- /dev/null +++ b/users/wpcarro/website/habit-screens/src/Utils.elm @@ -0,0 +1,37 @@ +module Utils exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Maybe.Extra + + +type Strategy + = Always String + | When Bool String + | If Bool String String + + +class : List Strategy -> Attribute msg +class classes = + classes + |> List.map + (\strategy -> + case strategy of + Always x -> + Just x + + When True x -> + Just x + + When False _ -> + Nothing + + If True x _ -> + Just x + + If False _ x -> + Just x + ) + |> Maybe.Extra.values + |> String.join " " + |> Html.Attributes.class diff --git a/users/wpcarro/website/index.html b/users/wpcarro/website/index.html new file mode 100644 index 000000000000..5176b2a9e808 --- /dev/null +++ b/users/wpcarro/website/index.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>wpcarro.dev</title> + </head> + <body> + <h1>Sitemap</h1> + + <ul> + <li>Documents</li> + <ul> + <li> + <a href="/habits">Habits</a> + </li> + </ul> + <li>Other</li> + <ul> + <li> + <a href="https://blog.wpcarro.dev">blog.wpcarro.dev</a> + </li> + <li> + <a href="https://learn.wpcarro.dev">learn.wpcarro.dev</a> + </li> + <li> + <a href="https://sandbox.wpcarro.dev">sandbox.wpcarro.dev</a> + </li> + </ul> + <li>Social</li> + <ul> + <li> + <a href="https://twitter.com/wpcarro">Twitter</a> + </li> + <li> + <a href="https://github.com/wpcarro">Github</a> + </li> + <li> + <a href="https://linkedin.com/in/williampatrickcarroll">LinkedIn</a> + </li> + </ul> + </ul> + + </body> +</html> diff --git a/users/wpcarro/website/learn/README.md b/users/wpcarro/website/learn/README.md new file mode 100644 index 000000000000..8f6f19ed5c50 --- /dev/null +++ b/users/wpcarro/website/learn/README.md @@ -0,0 +1,4 @@ +# Learn + +Hosting the content for my website `learn.wpcarro.dev`, where I advertise +teaching others how to program. diff --git a/users/wpcarro/website/learn/default.nix b/users/wpcarro/website/learn/default.nix new file mode 100644 index 000000000000..7aa8975a45fa --- /dev/null +++ b/users/wpcarro/website/learn/default.nix @@ -0,0 +1,10 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "learn.wpcarro.dev"; + src = ./static; + buildPhase = '' + cp -R $src $out + ''; + dontInstall = true; +} diff --git a/users/wpcarro/website/learn/static/index.html b/users/wpcarro/website/learn/static/index.html new file mode 100644 index 000000000000..1c421a43d715 --- /dev/null +++ b/users/wpcarro/website/learn/static/index.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="Learn to code" /> + <link + href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" + rel="stylesheet" + /> + <title>Learn to code</title> + </head> + <body class="font-serif container max-w-2xl mx-auto px-8"> + <h1 class="text-5xl text-center mt-12 mb-8">I can teach you to code.</h1> + <div class="my-4"> + <p class="leading-relaxed mb-4"> + My name is William. I have programmed as a professional for five years + and as a hobbyist for ten years. I am currently an engineer at Google, + but I have worked at small start-ups and medium-sized corporations. + </p> + <p class="leading-relaxed mb-4"> + Whether you have never written a single line of code, or you know how to + code and you are interested in going deeper, I can teach you a variety + of skills that professional software engineers use to succeed. + </p> + </div> + <div class="my-4"> + <h2 class="text-3xl">Why coding?</h2> + <p class="leading-relaxed mb-4"> + Are you interested in creating your own website? Perhaps you would like + to make your own video game. Maybe you like the notion of automating the + boring things in your life with code, but you do not know where to + start. + </p> + <p class="leading-relaxed mb-4"> + Coding is the most creative outlet in my life, and I say that as a + musician and a former architecture student. I know many people who want + to learn how to code, who know they can learn online, but they still + cannot code. Together we will overcome this barrier to entry. I will + teach you how to code, and then I will teach you how to take yourself + the rest of the way. <b>You will be coding from day one.</b> + </p> + </div> + <div class="my-4"> + <h2 class="text-3xl">Pricing</h2> + <p class="leading-relaxed mb-4"> + I charge <bold class="font-bold">ยฃ50</bold> per hour for video lessons + and <bold class="font-bold">ยฃ100</bold> per hour for in-person sessions. + I am currently based in South Kensington, London. + </p> + </div> + <div class="my-4"> + <h2 class="text-3xl">Contact</h2> + <p class="leading-relaxed mb-4"> + Whether you want to sign-up or simply want to learn more, send me an + email at + <a + href="mailto:wpcarro@gmail.com" + class="font-bold text-blue-600 hover:underline" + >wpcarro@gmail.com</a + >. + </p> + <p class="text-center my-8">Why delay? <em>Start today.</em></p> + </div> + <footer class="mb-8 lg:flex"> + <a + class="block py-2 lg:w-1/4 text-center hover:underline" + href="https://blog.wpcarro.dev" + >Blog</a + > + <a + class="block py-2 lg:w-1/4 text-center hover:underline" + href="https://linkedin.com/in/williampatrickcarroll" + >LinkedIn</a + > + <a + class="block py-2 lg:w-1/4 text-center hover:underline" + href="https://twitter.com/wpcarro" + >Twitter</a + > + <a + class="block py-2 lg:w-1/4 text-center hover:underline" + href="https://github.com/wpcarro" + >Github</a + > + </footer> + <!-- Global site tag (gtag.js) - Google Analytics --> + <script + async + src="https://www.googletagmanager.com/gtag/js?id=UA-160226702-1" + ></script> + <script> + window.dataLayer = window.dataLayer || []; + function gtag() { + dataLayer.push(arguments); + } + gtag("js", new Date()); + + gtag("config", "UA-160226702-1"); + </script> + </body> +</html> diff --git a/users/wpcarro/website/sandbox/contentful/.envrc b/users/wpcarro/website/sandbox/contentful/.envrc new file mode 100644 index 000000000000..848d74e8b5e6 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/.envrc @@ -0,0 +1,4 @@ +source_up +use_nix +export CONTENTFUL_SPACE_ID="$(jq -j '.contentful | .spaceId' < ~/briefcase/secrets.json)" +export CONTENTFUL_ACCESS_TOKEN="$(jq -j '.contentful | .accessToken' < ~/briefcase/secrets.json)" diff --git a/users/wpcarro/website/sandbox/contentful/.gitignore b/users/wpcarro/website/sandbox/contentful/.gitignore new file mode 100644 index 000000000000..fdf1c6188a4c --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/.gitignore @@ -0,0 +1,2 @@ +.cache +dist \ No newline at end of file diff --git a/users/wpcarro/website/sandbox/contentful/README.md b/users/wpcarro/website/sandbox/contentful/README.md new file mode 100644 index 000000000000..9bd6fc914bb6 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/README.md @@ -0,0 +1,18 @@ +# Contentful + +I have not used a CMS in a few years. I learned about Contentful from a +Gatsby.js tutorial, and I wanted to learn more; I created a Contentful account, +and I'm experimenting with the data here. + +## Developing + +```shell +$ nix-shell +$ yarn run dev +``` + +## Building + +```shell +$ nix-build +``` diff --git a/users/wpcarro/website/sandbox/contentful/default.nix b/users/wpcarro/website/sandbox/contentful/default.nix new file mode 100644 index 000000000000..f7125655ccdc --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/default.nix @@ -0,0 +1,19 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "ideal-website"; + src = builtins.path { path = ./.; name = "contentful"; }; + buildInputs = with pkgs; [ + nodejs + # Exposes lscpu for parcel.js + utillinux + ]; + # parcel.js needs number of CPUs + PARCEL_WORKERS = "1"; + buildPhase = '' + npx parcel build index.html + ''; + installPhase = '' + mv dist $out + ''; +} diff --git a/users/wpcarro/website/sandbox/contentful/package.json b/users/wpcarro/website/sandbox/contentful/package.json new file mode 100644 index 000000000000..3530bef763c9 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/package.json @@ -0,0 +1,26 @@ +{ + "name": "tailwindcss", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "npx parcel src/index.html & npx tsc --watch --noEmit" + }, + "devDependencies": { + "@types/node": "^13.9.3", + "parcel-bundler": "^1.12.4", + "tailwindcss": "^1.2.0", + "typescript": "^3.8.3" + }, + "dependencies": { + "@reduxjs/toolkit": "^1.2.5", + "@types/react-dom": "^16.9.5", + "@types/react-redux": "^7.1.7", + "@types/react-router-dom": "^5.1.3", + "contentful": "^7.14.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.1.2" + } +} diff --git a/users/wpcarro/website/sandbox/contentful/postcss.config.js b/users/wpcarro/website/sandbox/contentful/postcss.config.js new file mode 100644 index 000000000000..a23795075b11 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/postcss.config.js @@ -0,0 +1,5 @@ +const tailwindcss = require("tailwindcss"); + +module.exports = { + plugins: [tailwindcss("./tailwind.config.js")], +}; diff --git a/users/wpcarro/website/sandbox/contentful/shell.nix b/users/wpcarro/website/sandbox/contentful/shell.nix new file mode 100644 index 000000000000..083254beefd0 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/shell.nix @@ -0,0 +1,9 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + yarn + ]; +} diff --git a/users/wpcarro/website/sandbox/contentful/src/App.tsx b/users/wpcarro/website/sandbox/contentful/src/App.tsx new file mode 100644 index 000000000000..288f03321804 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/App.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from "react"; +import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import { actions, useTypedSelector } from "./store"; +import { Link } from "react-router-dom"; +import { getClient } from "./contentful"; +import type { Book } from "./store"; + +const App: React.FC = () => { + const dispatch = useDispatch(); + const { isLoading, books } = useTypedSelector((state) => ({ + isLoading: state.isLoading, + books: state.books, + })); + + useEffect(() => { + async function fetchData() { + const entries = await getClient().getEntries(); + const books = entries.items.map((x) => x.fields) as Book[]; + + dispatch(actions.setBooks(books)); + } + fetchData(); + }, []); + + return ( + <Router> + <Switch> + <Route exact path="/"> + <div className="container mx-auto"> + <h1 className="py-6 text-2xl">Books</h1> + <ul> + {books.map((book) => ( + <li key={book.title} className="py-3"> + <p> + <span className="font-bold pr-3">{book.title}</span> + <span className="text-gray-600">{book.author}</span> + </p> + </li> + ))} + </ul> + </div> + </Route> + </Switch> + </Router> + ); +}; + +export default App; diff --git a/users/wpcarro/website/sandbox/contentful/src/contentful.ts b/users/wpcarro/website/sandbox/contentful/src/contentful.ts new file mode 100644 index 000000000000..02ebc92b68df --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/contentful.ts @@ -0,0 +1,27 @@ +import { createClient } from "contentful"; +import type { ContentfulClientApi } from "contentful"; + +const space = process.env.CONTENTFUL_SPACE_ID; +const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN; + +let client: ContentfulClientApi; + +// Idempotent way to get a reference to the Contentful client. +export const getClient = (): ContentfulClientApi => { + if (typeof client !== "undefined") { + return client; + } else { + if (typeof space === "string" && typeof accessToken === "string") { + let client = createClient({ + space, + accessToken, + }); + + return client; + } else { + throw new Error( + "Please set CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN" + ); + } + } +}; diff --git a/users/wpcarro/website/sandbox/contentful/src/index.css b/users/wpcarro/website/sandbox/contentful/src/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/website/sandbox/contentful/src/index.html b/users/wpcarro/website/sandbox/contentful/src/index.html new file mode 100644 index 000000000000..91752af916a4 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="./index.css" /> + </head> + <body> + <div id="mount"></div> + <script src="./index.tsx"></script> + </body> +</html> diff --git a/users/wpcarro/website/sandbox/contentful/src/index.tsx b/users/wpcarro/website/sandbox/contentful/src/index.tsx new file mode 100644 index 000000000000..dc28dc4a9cc8 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/index.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import { Provider } from "react-redux"; +import store from "./store"; + +ReactDOM.render( + <Provider store={store}> + <App /> + </Provider>, + document.getElementById("mount") +); diff --git a/users/wpcarro/website/sandbox/contentful/src/store.ts b/users/wpcarro/website/sandbox/contentful/src/store.ts new file mode 100644 index 000000000000..b02053d302b6 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/src/store.ts @@ -0,0 +1,36 @@ +import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit"; +import { useSelector, TypedUseSelectorHook } from "react-redux"; + +export interface Book { + title: string; + author: string; + // TODO(wpcarro): Prefer datetime type here. + publicationDate: string; +} + +export interface State { + isLoading: boolean; + books: Book[]; +} + +const initialState: State = { + isLoading: true, + books: [], +}; + +export const { actions, reducer } = createSlice({ + name: "application", + initialState, + reducers: { + toggleIsLoading: (state) => ({ ...state, isLoading: !state.isLoading }), + setBooks: (state, action) => ({ ...state, books: action.payload }), + }, +}); + +/** + * Defining and consuming this allows us to avoid annotating State in all of our + * selectors. + */ +export const useTypedSelector: TypedUseSelectorHook<State> = useSelector; + +export default configureStore({ reducer }); diff --git a/users/wpcarro/website/sandbox/contentful/tailwind.config.js b/users/wpcarro/website/sandbox/contentful/tailwind.config.js new file mode 100644 index 000000000000..3da6fa0dc7b7 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + theme: { + extend: {}, + }, + variants: {}, + plugins: [], +}; diff --git a/users/wpcarro/website/sandbox/contentful/tsconfig.json b/users/wpcarro/website/sandbox/contentful/tsconfig.json new file mode 100644 index 000000000000..fe07ec1da4d4 --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src/**/*"] +} diff --git a/users/wpcarro/website/sandbox/contentful/yarn.lock b/users/wpcarro/website/sandbox/contentful/yarn.lock new file mode 100644 index 000000000000..45fdea32b7da --- /dev/null +++ b/users/wpcarro/website/sandbox/contentful/yarn.lock @@ -0,0 +1,5717 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== + dependencies: + browserslist "^4.9.1" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.4", "@babel/generator@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" + integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== + dependencies: + "@babel/types" "^7.9.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-builder-react-jsx-experimental@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" + integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-module-imports" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-builder-react-jsx@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" + integrity sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-compilation-targets@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" + integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== + dependencies: + "@babel/compat-data" "^7.8.6" + browserslist "^4.9.1" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== + dependencies: + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" + integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.4.4", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.3.tgz#043a5fc2ad8b7ea9facddc4e802a1f0f25da7255" + integrity sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A== + +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" + integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.8" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-flow@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz#f2c883bd61a6316f2c89380ae5122f923ba4527f" + integrity sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" + integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" + integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" + integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-split-export-declaration" "^7.8.3" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" + integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-flow-strip-types@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" + integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-flow" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-modules-amd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== + dependencies: + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.8.7": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" + integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.1.tgz#d03af29396a6dc51bfa24eefd8005a9fd381152a" + integrity sha512-+xIZ6fPoix7h57CNO/ZeYADchg1tFyX9NDsnmNFFua8e1JNPln156mzS+8AQe1On2X2GLlANHJWHIXbMCqWDkQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.9.0" + "@babel/helper-builder-react-jsx-experimental" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" + integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" + integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.0" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.0" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.4.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" + integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" + integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@iarna/toml@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" + integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@parcel/fs@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-1.11.0.tgz#fb8a2be038c454ad46a50dc0554c1805f13535cd" + integrity sha512-86RyEqULbbVoeo8OLcv+LQ1Vq2PKBAvWTU9fCgALxuCTbbs5Ppcvll4Vr+Ko1AnmMzja/k++SzNAwJfeQXVlpA== + dependencies: + "@parcel/utils" "^1.11.0" + mkdirp "^0.5.1" + rimraf "^2.6.2" + +"@parcel/logger@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" + integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== + dependencies: + "@parcel/workers" "^1.11.0" + chalk "^2.1.0" + grapheme-breaker "^0.3.2" + ora "^2.1.0" + strip-ansi "^4.0.0" + +"@parcel/utils@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" + integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== + +"@parcel/watcher@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" + integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== + dependencies: + "@parcel/utils" "^1.11.0" + chokidar "^2.1.5" + +"@parcel/workers@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-1.11.0.tgz#7b8dcf992806f4ad2b6cecf629839c41c2336c59" + integrity sha512-USSjRAAQYsZFlv43FUPdD+jEGML5/8oLF0rUzPQTtK4q9kvaXr49F5ZplyLz5lox78cLZ0TxN2bIDQ1xhOkulQ== + dependencies: + "@parcel/utils" "^1.11.0" + physical-cpu-count "^2.0.0" + +"@reduxjs/toolkit@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.2.5.tgz#149aa62da12a18a67a30495cb63fd897003f2272" + integrity sha512-/OWoW5mniUXAomw4+3ZhhWodcs1/SRvK2HKyxLXdW6vKgmJhiBiSHe/huHARlKWujEmGaJrkafx548GE494bCQ== + dependencies: + immer "^4.0.1" + redux "^4.0.0" + redux-devtools-extension "^2.13.8" + redux-immutable-state-invariant "^2.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@^13.9.3": + version "13.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" + integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" + integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + +"@types/react-dom@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7" + integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a" + integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" + integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*": + version "16.9.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e" + integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn-walk@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + +acorn@^6.0.1, acorn@^6.0.4: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +ansi-to-html@^0.6.4: + version "0.6.14" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" + integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== + dependencies: + entities "^1.1.2" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.4.5: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + dependencies: + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.26" + postcss-value-parser "^4.0.2" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +axios@^0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-runtime@^6.11.6, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-types@^6.15.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon-walk@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babylon-walk/-/babylon-walk-1.0.2.tgz#3b15a5ddbb482a78b4ce9c01c8ba181702d9d6ce" + integrity sha1-OxWl3btIKni0zpwByLoYFwLZ1s4= + dependencies: + babel-runtime "^6.11.6" + babel-types "^6.15.0" + lodash.clone "^4.5.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brfs@^1.2.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" + integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== + dependencies: + quote-stream "^1.0.1" + resolve "^1.1.5" + static-module "^2.2.0" + through2 "^2.0.0" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.8.3, browserslist@^4.9.1: + version "4.11.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.0.tgz#aef4357b10a8abda00f97aac7cd587b2082ba1ad" + integrity sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.380" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035: + version "1.0.30001036" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" + integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" + integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" + integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== + +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@~1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +contentful-resolve-response@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.1.4.tgz#9eb656876eecb2cd00444f0adf26bd91a5ec1992" + integrity sha512-oFq6n6zjbiwD9/7mBa8YHPwvPM0B0D4uOgg1n/rVzpQPhCrzeIixNj6fbJAbDiJt05rZqxiY3K1Db7pPRhRaZw== + dependencies: + lodash "^4.17.4" + +contentful-sdk-core@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/contentful-sdk-core/-/contentful-sdk-core-6.4.0.tgz#3b42991ae9084baf1bc5d01c61cb54441f740803" + integrity sha512-UvYQ/Wrt5EntlMSBbgqgvKfTBRzf6fIT2p5Wp7bsnA3/KLEiYcYd/2qhUKw4x9nfp+0G8B1s4TpDwxV0oymBiA== + dependencies: + lodash "^4.17.10" + qs "^6.5.2" + +contentful@^7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/contentful/-/contentful-7.14.0.tgz#3b57287e484b8370adfd654a5196be2c2ffb9afa" + integrity sha512-edoiQx0AkmNqnGofmLHGVt84k2S8XuPyw2UOct/Oc3HEW0Z66osMJ4M/XA9GeByCCD5ZC7qotseBRyag/1g0iA== + dependencies: + axios "^0.19.1" + contentful-resolve-response "^1.1.4" + contentful-sdk-core "^6.4.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.11" + +convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + dependencies: + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^2.4.0, core-js@^2.6.5: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.4: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-modules-loader-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" + integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= + dependencies: + icss-replace-symbols "1.1.0" + postcss "6.0.1" + postcss-modules-extract-imports "1.1.0" + postcss-modules-local-by-default "1.2.0" + postcss-modules-scope "1.1.0" + postcss-modules-values "1.3.0" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-selector-tokenizer@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" + integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + regexpu-core "^4.6.0" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= + +css-what@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" + integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.0.0, cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" + integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + dependencies: + css-tree "1.0.0-alpha.37" + +cssom@0.3.x, cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +csstype@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +deasync@^0.1.14: + version "0.1.19" + resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.19.tgz#e7ea89fcc9ad483367e8a48fe78f508ca86286e8" + integrity sha512-oh3MRktfnPlLysCPpBpKZZzb4cUC/p0aA3SyRGp15lN30juJBTo/CiD0d4fR+f1kBtUQoJj1NE9RPNWQ7BQ9Mg== + dependencies: + bindings "^1.5.0" + node-addon-api "^1.7.1" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.380: + version "1.3.381" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.381.tgz#952678ff91a5f36175a3832358a6dd2de3bf62b7" + integrity sha512-JQBpVUr83l+QOqPQpj2SbOve1bBE4ACpmwcMNqWlZmfib7jccxJ02qFNichDpZ5LS4Zsqc985NIPKegBIZjK8Q== + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +entities@^1.1.1, entities@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +envinfo@^7.3.1: + version "7.5.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" + integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.11.0, escodegen@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +falafel@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" + integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + dependencies: + acorn "^7.1.1" + foreach "^2.0.5" + isarray "^2.0.1" + object-keys "^1.0.6" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-glob@^2.2.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +filesize@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +grapheme-breaker@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz#5b9e6b78c3832452d2ba2bb1cb830f96276410ac" + integrity sha1-W55reMODJFLSuiuxy4MPlidkEKw= + dependencies: + brfs "^1.2.0" + unicode-trie "^0.3.1" + +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-tags@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" + integrity sha1-x43mW1Zjqll5id0rerSSANfk25g= + +htmlnano@^0.2.2: + version "0.2.5" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-0.2.5.tgz#134fd9548c7cbe51c8508ce434a3f9488cff1b0b" + integrity sha512-X1iPSwXG/iF9bVs+/obt2n6F64uH0ETkA8zp7qFDmLW9/+A6ueHGeb/+qD67T21qUY22owZPMdawljN50ajkqA== + dependencies: + cssnano "^4.1.10" + normalize-html-whitespace "^1.0.0" + posthtml "^0.12.0" + posthtml-render "^1.1.5" + purgecss "^1.4.0" + svgo "^1.3.2" + terser "^4.3.9" + uncss "^0.17.2" + +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +immer@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-4.0.2.tgz#9ff0fcdf88e06f92618a5978ceecb5884e633559" + integrity sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-html@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-html/-/is-html-1.1.0.tgz#e04f1c18d39485111396f9a0273eab51af218464" + integrity sha1-4E8cGNOUhRETlvmgJz6rUa8hhGQ= + dependencies: + html-tags "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isarray@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" + integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== + dependencies: + vlq "^0.2.2" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + dependencies: + source-map "^0.5.6" + +merge2@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-libs-browser@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" + +normalize-html-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" + integrity sha512-9ui7CGtOOlehQu0t/OhhlmDyc71mKVlv+4vF+me4iZLPrNtRL2xoquEdfZxasC/bdQi/Hr3iTrpyRKIG+ocabA== + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-inspect@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" + integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-2.1.0.tgz#6caf2830eb924941861ec53a173799e008b51e5b" + integrity sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA== + dependencies: + chalk "^2.3.1" + cli-cursor "^2.1.0" + cli-spinners "^1.1.0" + log-symbols "^2.2.0" + strip-ansi "^4.0.0" + wcwidth "^1.0.1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parcel-bundler@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" + integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.4.4" + "@babel/generator" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/plugin-transform-flow-strip-types" "^7.4.4" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/preset-env" "^7.4.4" + "@babel/runtime" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + "@iarna/toml" "^2.2.0" + "@parcel/fs" "^1.11.0" + "@parcel/logger" "^1.11.1" + "@parcel/utils" "^1.11.0" + "@parcel/watcher" "^1.12.1" + "@parcel/workers" "^1.11.0" + ansi-to-html "^0.6.4" + babylon-walk "^1.0.2" + browserslist "^4.1.0" + chalk "^2.1.0" + clone "^2.1.1" + command-exists "^1.2.6" + commander "^2.11.0" + core-js "^2.6.5" + cross-spawn "^6.0.4" + css-modules-loader-core "^1.1.0" + cssnano "^4.0.0" + deasync "^0.1.14" + dotenv "^5.0.0" + dotenv-expand "^5.1.0" + envinfo "^7.3.1" + fast-glob "^2.2.2" + filesize "^3.6.0" + get-port "^3.2.0" + htmlnano "^0.2.2" + is-glob "^4.0.0" + is-url "^1.2.2" + js-yaml "^3.10.0" + json5 "^1.0.1" + micromatch "^3.0.4" + mkdirp "^0.5.1" + node-forge "^0.7.1" + node-libs-browser "^2.0.0" + opn "^5.1.0" + postcss "^7.0.11" + postcss-value-parser "^3.3.1" + posthtml "^0.11.2" + posthtml-parser "^0.4.0" + posthtml-render "^1.1.3" + resolve "^1.4.0" + semver "^5.4.1" + serialize-to-js "^3.0.0" + serve-static "^1.12.4" + source-map "0.6.1" + terser "^3.7.3" + v8-compile-cache "^2.0.0" + ws "^5.1.1" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +physical-cpu-count@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" + integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-calc@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" + integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-nested@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248" + integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw== + dependencies: + postcss "^7.0.21" + postcss-selector-parser "^6.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-selector-parser@6.0.2, postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1, postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27: + version "7.0.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" + integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +posthtml-parser@^0.4.0, posthtml-parser@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" + integrity sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg== + dependencies: + htmlparser2 "^3.9.2" + +posthtml-render@^1.1.3, posthtml-render@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.2.0.tgz#3df0c800a8bbb95af583a94748520469477addf4" + integrity sha512-dQB+hoAKDtnI94RZm/wxBUH9My8OJcXd0uhWmGh2c7tVtQ85A+OS3yCN3LNbFtPz3bViwBJXAeoi+CBGMXM0DA== + +posthtml@^0.11.2: + version "0.11.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" + integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +posthtml@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.12.0.tgz#6e2a2fcd774eaed1a419a95c5cc3a92b676a40a6" + integrity sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +purgecss@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.4.2.tgz#67ab50cb4f5c163fcefde56002467c974e577f41" + integrity sha512-hkOreFTgiyMHMmC2BxzdIw5DuC6kxAbP/gGOGd3MEsF3+5m69rIvUEPaxrnoUtfODTFKe9hcXjGwC6jcjoyhOw== + dependencies: + glob "^7.1.3" + postcss "^7.0.14" + postcss-selector-parser "^6.0.0" + yargs "^14.0.0" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@^6.5.2: + version "6.9.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.2.tgz#a27b695006544a04bf0e6c6a7e8120778926d5bd" + integrity sha512-2eQ6zajpK7HwqrY1rRtGw5IZvjgtELXzJECaEDuzDFo2jjnIXpJSimzd4qflWZq6bLLi+Zgfj5eDrAzl/lptyg== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +quote-stream@^1.0.1, quote-stream@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" + integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + dependencies: + buffer-equal "0.0.1" + minimist "^1.1.3" + through2 "^2.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-dom@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-redux@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +redux-devtools-extension@^2.13.8: + version "2.13.8" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" + integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== + +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpu-core@^4.6.0, regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.5, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.4.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-to-js@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" + integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== + +serve-static@^1.12.4: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-copy@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +static-eval@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.5.tgz#f0782e66999c4b3651cda99d9ce59c507d188f71" + integrity sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA== + dependencies: + escodegen "^1.11.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +static-module@^2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" + integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== + dependencies: + concat-stream "~1.6.0" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "~1.9.0" + falafel "^2.1.0" + has "^1.0.1" + magic-string "^0.22.4" + merge-source-map "1.0.4" + object-inspect "~1.4.0" + quote-stream "~1.0.2" + readable-stream "~2.3.3" + shallow-copy "~0.0.1" + static-eval "^2.0.0" + through2 "~2.0.3" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0, svgo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291" + integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ== + dependencies: + autoprefixer "^9.4.5" + bytes "^3.0.0" + chalk "^3.0.0" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +terser@^3.7.3: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + +terser@^4.3.9: + version "4.6.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" + integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0, through2@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +uncss@^0.17.2: + version "0.17.3" + resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.3.tgz#50fc1eb4ed573ffff763458d801cd86e4d69ea11" + integrity sha512-ksdDWl81YWvF/X14fOSw4iu8tESDHFIeyKIeDrK6GEVTQvqJc1WlOEXqostNwOCi3qAj++4EaLsdAgPmUbEyog== + dependencies: + commander "^2.20.0" + glob "^7.1.4" + is-absolute-url "^3.0.1" + is-html "^1.1.0" + jsdom "^14.1.0" + lodash "^4.17.15" + postcss "^7.0.17" + postcss-selector-parser "6.0.2" + request "^2.88.0" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unicode-trie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" + integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^5.1.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3" + integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^14.0.0: + version "14.2.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" + integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== + dependencies: + cliui "^5.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^15.0.1" diff --git a/users/wpcarro/website/sandbox/covid-uk/default.nix.ignore b/users/wpcarro/website/sandbox/covid-uk/default.nix.ignore new file mode 100644 index 000000000000..309b1fa64b4d --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/default.nix.ignore @@ -0,0 +1,16 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "covid-uk"; + buildInputs = []; + src = builtins.path { path = ./.; name = "covid-uk"; }; + # TODO(wpcarro): Need to run `yarn install` somehow. + # TODO(wpcarro): Need to run `npx tailwindcss build styles.css -o output.css`. + buildPhase = '' + mkdir -p $out + mkdir -p $out/node_modules/chart.js/dist + cp $src/node_modules/chart.js/dist/Chart.bundle.min.js $out/node_modules/chart.js/dist + cp $src/index.html $src/output.css $out + ''; + dontInstall = true; +} diff --git a/users/wpcarro/website/sandbox/covid-uk/index.html b/users/wpcarro/website/sandbox/covid-uk/index.html new file mode 100644 index 000000000000..15769f7490e0 --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/index.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>COVID-19 UK</title> + <link rel="stylesheet" href="output.css"> +</head> +<body class="container mx-auto py-10"> + <div> + <h1 class="text-center">COVID-19 in the UK</h1> + <p> + Up until recently, I used a couple of resources (i.e. + <a href="https://multimedia.scmp.com/infographics/news/china/article/3047038/wuhan-virus/index.html">one</a>, + <a href="https://www.worldometers.info/coronavirus/">two</a>) for tracking + an updated number of confirmed covid-19 cases. + </p> + <p> + Given the high speed at which the virus is spreading, I was having a + difficult time intuiting the shape of this growth. For example if today + the total number of confirmed cases for covid-19 in the UK was 500, I + could not remember if yesterday it was 450, 400, or 200. + </p> + <p> + Thankfully someone is <a + href="https://github.com/pomber/covid19">publishing this data</a> as a + timeseries database. I am currently living in London, so I decided to + chart the <u>daily number of confirmed covid-19 cases in the UK</u> to + better understand what is happening. + </p> + </div> + <canvas id="myChart" class="py-12"></canvas> + <script src="./node_modules/chart.js/dist/Chart.bundle.min.js"></script> + <script> + var timeseries = + fetch('https://pomber.github.io/covid19/timeseries.json') + .then(res => res.json()) + .then(createChart); + + function createChart(data) { + var uk = data["United Kingdom"]; + var data = uk.map(x => x["confirmed"]); + var labels = uk.map(x => x["date"]); + + var ctx = document.getElementById('myChart').getContext('2d'); + var myChart = new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: [{ + label: 'Number of confirmed COVID-19 cases in the U.K.', + data: data, + backgroundColor: 'rgba(255, 0, 100, 0.2)', + borderWidth: 3 + }] + }, + options: { + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } + }); + } + </script> + <div> + <h2 class="text-center">Back of the envelope predictions</h2> + <p> + From what I have read, a population where 60% of its constituents have + been infected with covid-19 and have recovered is said to have "herd + immunity". Once a population has herd immunity, the rate at which the + virus spreads decreases. + </p> + <p> + Roughly 60M people live in the UK; 60% of 60M is around 40M. Before a + population reaches "herd immunity", the total number of <em>true + covid-19 cases</em> <u>doubles every five days</u>. Therefore in <u>fifty + days</u> you might expect the number of true cases to be <u>1000x + larger</u> than what it is today. + </p> + <p> + So if you think the total number of <em>true covid-19 cases</em> + <u>today</u> is 40,000 then you might expect the rate of growth to slow + down in a little less than two months. + </p> + <p> + Thank you for reading. + </p> + </div> + <footer class="pt-5 mb-8 lg:flex"> + <a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://learn.wpcarro.dev">Learn</a> + <a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://blog.wpcarro.dev">Blog</a> + <a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://twitter.com/wpcarro">Twitter</a> + <a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://github.com/wpcarro">Github</a> + </footer> +</body> +</html> diff --git a/users/wpcarro/website/sandbox/covid-uk/package.json b/users/wpcarro/website/sandbox/covid-uk/package.json new file mode 100644 index 000000000000..939506c8cce1 --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/package.json @@ -0,0 +1,16 @@ +{ + "name": "covid-uk", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "chart.js": "^2.9.3", + "tailwindcss": "^1.2.0" + } +} diff --git a/users/wpcarro/website/sandbox/covid-uk/shell.nix b/users/wpcarro/website/sandbox/covid-uk/shell.nix new file mode 100644 index 000000000000..6442c39f9c47 --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/shell.nix @@ -0,0 +1,9 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + yarn + nodejs + ]; +} diff --git a/users/wpcarro/website/sandbox/covid-uk/styles.css b/users/wpcarro/website/sandbox/covid-uk/styles.css new file mode 100644 index 000000000000..1f48906d8ffc --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/styles.css @@ -0,0 +1,28 @@ +@tailwind base; + +body { + @apply font-mono; +} + +h1 { + @apply text-3xl mb-5 font-bold; +} + +h2 { + @apply text-2xl mb-5 font-bold; +} + +h3 { + @apply text-xl mb-5 font-bold; +} + +a { + @apply text-blue-600 underline; +} + +p { + @apply mt-2 mb-5; +} + +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/website/sandbox/covid-uk/tailwind.config.js b/users/wpcarro/website/sandbox/covid-uk/tailwind.config.js new file mode 100644 index 000000000000..af829e20f9cb --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + theme: { + extend: {}, + }, + variants: {}, + plugins: [], +} diff --git a/users/wpcarro/website/sandbox/covid-uk/yarn.lock b/users/wpcarro/website/sandbox/covid-uk/yarn.lock new file mode 100644 index 000000000000..3e66831c9e82 --- /dev/null +++ b/users/wpcarro/website/sandbox/covid-uk/yarn.lock @@ -0,0 +1,542 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + +acorn@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +autoprefixer@^9.4.5: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + dependencies: + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.26" + postcss-value-parser "^4.0.2" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browserslist@^4.8.3: + version "4.10.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9" + integrity sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.378" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035: + version "1.0.30001035" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e" + integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ== + +chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chart.js@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7" + integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw== + dependencies: + chartjs-color "^2.1.0" + moment "^2.10.2" + +chartjs-color-string@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" + integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== + dependencies: + color-name "^1.0.0" + +chartjs-color@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0" + integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== + dependencies: + chartjs-color-string "^0.6.0" + color-convert "^1.9.3" + +color-convert@^1.9.0, color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +css-unit-converter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +electron-to-chromium@^1.3.378: + version "1.3.379" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.379.tgz#81dc5e82a3e72bbb830d93e15bc35eda2bbc910e" + integrity sha512-NK9DBBYEBb5f9D7zXI0hiE941gq3wkBeQmXs1ingigA/jnTg5mhwY2Z5egwA+ZI8OLGKCx0h1Cl8/xeuIBuLlg== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.2: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +moment@^2.10.2: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-nested@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248" + integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw== + dependencies: + postcss "^7.0.21" + postcss-selector-parser "^6.0.2" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + +postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26: + version "7.0.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" + integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +resolve@^1.14.2: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +tailwindcss@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291" + integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ== + dependencies: + autoprefixer "^9.4.5" + bytes "^3.0.0" + chalk "^3.0.0" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== diff --git a/users/wpcarro/website/sandbox/default.nix.ignore b/users/wpcarro/website/sandbox/default.nix.ignore new file mode 100644 index 000000000000..4f86b49002a5 --- /dev/null +++ b/users/wpcarro/website/sandbox/default.nix.ignore @@ -0,0 +1,13 @@ +{ pkgs, briefcase, ... }: + +pkgs.stdenv.mkDerivation { + name = "covid-uk"; + buildInputs = []; + src = builtins.path { path = ./.; name = "sandbox"; }; + buildPhase = '' + mkdir -p $out + cp $src/index.html $out + cp -r ${briefcase.website.sandbox.covid-uk} $out/covid-uk + ''; + dontInstall = true; +} diff --git a/users/wpcarro/website/sandbox/github-issues-service/README.md b/users/wpcarro/website/sandbox/github-issues-service/README.md new file mode 100644 index 000000000000..2af860014378 --- /dev/null +++ b/users/wpcarro/website/sandbox/github-issues-service/README.md @@ -0,0 +1,28 @@ +# Github Issues Service (GIS) + +> 'Cause I got issues. But you got 'em too... +> - [Issues by Julia Michaels][issues] + +You have a website and your users want to request features or report bugs. How +do they do this? + +Our robot, GIS, can help you. GIS adds a widget to your website that allows +users to easily request features and report bugs. + +## Getting Started + +If Github is hosting your website's source code, you're ready to start using +GIS. GIS works with public and private repositories. + +Let's adopt Github's notion of "issues" to group feature requests and bug +reports together. When users click the GIS widget to create an issue, GIS +displays a modal form that the user completes. When the user submits the form, +GIS creates an issue on your Github repository. Now your team can use all of +Github's rich issue-tracking tools to manage your issues. + +## Installation + +To add GIS to your website, register your Github repository with us and we'll +give you a snippet to add to your website's HTML. It's that simple. + +[issues]: https://www.youtube.com/watch?v=9Ke4480MicU diff --git a/users/wpcarro/website/sandbox/index.html b/users/wpcarro/website/sandbox/index.html new file mode 100644 index 000000000000..ecd5475af266 --- /dev/null +++ b/users/wpcarro/website/sandbox/index.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>sandbox.wpcarro.dev</title> + </head> + <body> + <h1>Projects</h1> + <ul> + <li> + <a href="/covid-uk">COVID-19 in the UK</a> + </li> + </ul> + </body> +</html> diff --git a/users/wpcarro/website/sandbox/learnpianochords/.envrc b/users/wpcarro/website/sandbox/learnpianochords/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/website/sandbox/learnpianochords/.gitignore b/users/wpcarro/website/sandbox/learnpianochords/.gitignore new file mode 100644 index 000000000000..fd85a05d53d4 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css \ No newline at end of file diff --git a/users/wpcarro/website/sandbox/learnpianochords/README.md b/users/wpcarro/website/sandbox/learnpianochords/README.md new file mode 100644 index 000000000000..2527f4b96353 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/README.md @@ -0,0 +1,57 @@ +# Learn Piano Chords (LPC) + +Are you a musician looking for a more effective way to improve your craft? Maybe +you're a music teacher looking to create useful exercises to give your students. + +Studying music theory can be a fruitful undertaking, but it can often overwhelm +or bore students. I think that if practicing is enjoyable, students will +practice more. Practice doesn't make perfect; *perfect* practice makes perfect. +Learn Piano Chords is a web app that lowers the barrier to practicing and +internalizing music theory. + +## How does it work? + +1. Grab a cell phone or a laptop and your instrument. +2. Open a web browser and visit the Learn Piano Chords app (URL and app + forthcoming). +6. Set the tempo (i.e. pace) at which you would like to practice. +4. Set the target duration of your session. +5. Select the key(s) and chord(s) you would like to practice. +7. LPC will display chords at various rhythmic intervals during your practice + session. It is your job to play these chords in time before the next chord + appears. + +## Highlights + +Here are some useful features of LPC: +- Tempo: Set the rate at which LPC displays chords. +- Predefined practice sessions: LPC offers users a few practice sessions to get + users started. The goal, however, is to teach users to create their own + bespoke practice sessions. LPC aims to foster a community of practitioners who + curate and share their practice sessions. +- Whitelist / blacklist: Construct the set of chords you would like to + practice. Let's say you only want to practice triads in the keys of F, C, and + G. Would you also like to avoid diminished chords? Or maybe you *only* want to + practice major-7th chords for *all* keys. LPC supports all of these scenarios + and many others. You can save these chord configurations to reuse them at any + time. You can also share chord configurations with other LPC users if you find + the practice useful. +- Inversions: Every chord has inversions. For instance, every triad (i.e. chord + composed of three notes) has three inversions: root, second, and third + positions. LPC acknowledges all of the positions in which chords may appear + and helps you study all, some, or none of these inversions. +- Harmony: LPC understands basic harmony and can sort the chords you would like + to train in various harmonious permutations. +- Chaos-mode: Feeling confident? Throw the classical notions of harmony to the + wayside and use LPC in "chaos-mode" where LPC samples randomly from the Circle + of Fifths. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ elm-live -- src/Main.elm --output=elm.js +``` diff --git a/users/wpcarro/website/sandbox/learnpianochords/default.nix b/users/wpcarro/website/sandbox/learnpianochords/default.nix new file mode 100644 index 000000000000..37dfd4d390f5 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/default.nix @@ -0,0 +1,60 @@ +{ pkgs ? <nixpkgs>, ... }: + +with pkgs; + +let + mkDerivation = + { srcs ? ./elm-srcs.nix + , src + , name + , srcdir ? "./src" + , targets ? [] + , registryDat ? ./registry.dat + , outputJavaScript ? false + }: + stdenv.mkDerivation { + inherit name src; + + buildInputs = [ elmPackages.elm ] + ++ lib.optional outputJavaScript nodePackages_10_x.uglify-js; + + buildPhase = pkgs.elmPackages.fetchElmDeps { + elmPackages = import srcs; + elmVersion = "0.19.1"; + inherit registryDat; + }; + + installPhase = let + elmfile = module: "${srcdir}/${builtins.replaceStrings ["."] ["/"] module}.elm"; + extension = if outputJavaScript then "js" else "html"; + in '' + mkdir -p $out/share/doc + ${lib.concatStrings (map (module: '' + echo "compiling ${elmfile module}" + elm make ${elmfile module} --output $out/${module}.${extension} --docs $out/share/doc/${module}.json + ${lib.optionalString outputJavaScript '' + echo "minifying ${elmfile module}" + uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \ + | uglifyjs --mangle --output=$out/${module}.min.${extension} + ''} + '') targets)} + ''; + }; + mainDotElm = mkDerivation { + name = "elm-app-0.1.0"; + srcs = ./elm-srcs.nix; + src = builtins.path { path = ./.; name = "learnpianochords"; }; + targets = ["Main"]; + srcdir = "./src"; + outputJavaScript = true; + }; +in stdenv.mkDerivation { + name = "learn-piano-chords"; + buildInputs = []; + src = builtins.path { path = ./.; name = "learnpianochords"; }; + buildPhase = '' + mkdir -p $out + cp index.html output.css ${mainDotElm}/Main.min.js $out + ''; + dontInstall = true; +} diff --git a/users/wpcarro/website/sandbox/learnpianochords/elm-srcs.nix b/users/wpcarro/website/sandbox/learnpianochords/elm-srcs.nix new file mode 100644 index 000000000000..2823b430f887 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/elm-srcs.nix @@ -0,0 +1,67 @@ +{ + + "elm-community/maybe-extra" = { + sha256 = "0qslmgswa625d218djd3p62pnqcrz38f5p558mbjl6kc1ss0kzv3"; + version = "5.2.0"; + }; + + "elm/html" = { + sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; + version = "1.0.0"; + }; + + "elm-community/random-extra" = { + sha256 = "1dg2nz77w2cvp16xazbdsxkkw0xc9ycqpkd032faqdyky6gmz9g6"; + version = "3.1.0"; + }; + + "elm/svg" = { + sha256 = "1cwcj73p61q45wqwgqvrvz3aypjyy3fw732xyxdyj6s256hwkn0k"; + version = "1.0.1"; + }; + + "elm/browser" = { + sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13"; + version = "1.0.2"; + }; + + "elm/core" = { + sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; + version = "1.0.5"; + }; + + "elm-community/list-extra" = { + sha256 = "1ayv3148drynqnxdfwpjxal8vwzgsjqanjg7yxp6lhdcbkxgd3vd"; + version = "8.2.3"; + }; + + "elm/random" = { + sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl"; + version = "1.0.0"; + }; + + "elm/time" = { + sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; + version = "1.0.0"; + }; + + "elm/json" = { + sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh"; + version = "1.1.3"; + }; + + "owanturist/elm-union-find" = { + sha256 = "13gm7msnp0gr1lqia5m7m4lhy3m6kvjg37d304whb3psn88wqhj5"; + version = "1.0.0"; + }; + + "elm/url" = { + sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; + version = "1.0.0"; + }; + + "elm/virtual-dom" = { + sha256 = "0q1v5gi4g336bzz1lgwpn5b1639lrn63d8y6k6pimcyismp2i1yg"; + version = "1.0.2"; + }; +} diff --git a/users/wpcarro/website/sandbox/learnpianochords/elm.json b/users/wpcarro/website/sandbox/learnpianochords/elm.json new file mode 100644 index 000000000000..a95f80408ec4 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/elm.json @@ -0,0 +1,30 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/website/sandbox/learnpianochords/ideas.org b/users/wpcarro/website/sandbox/learnpianochords/ideas.org new file mode 100644 index 000000000000..4c2372280ed5 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/ideas.org @@ -0,0 +1,3 @@ +* Support a frequency table of all of the chords +* Support using spaced-repetition to help populate the frequency table of chords +* If doing a frequency table, support left and right hands diff --git a/users/wpcarro/website/sandbox/learnpianochords/index.css b/users/wpcarro/website/sandbox/learnpianochords/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/website/sandbox/learnpianochords/index.html b/users/wpcarro/website/sandbox/learnpianochords/index.html new file mode 100644 index 000000000000..5687c29eb7d0 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/index.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Learn Piano Chords</title> + <link rel="stylesheet" href="./output.css" /> + <script src="./Main.min.js"></script> + </head> + <body class="font-serif"> + <div id="mount"></div> + <script> + Elm.Main.init({node: document.getElementById("mount")}); + </script> + </body> +</html> diff --git a/users/wpcarro/website/sandbox/learnpianochords/registry.dat b/users/wpcarro/website/sandbox/learnpianochords/registry.dat new file mode 100644 index 000000000000..a73307ccda04 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/registry.dat Binary files differdiff --git a/users/wpcarro/website/sandbox/learnpianochords/shell.nix b/users/wpcarro/website/sandbox/learnpianochords/shell.nix new file mode 100644 index 000000000000..00bb4b0b3edc --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs.elmPackages; [ + elm + elm-format + elm-live + ]; +} diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/FlashCard.elm b/users/wpcarro/website/sandbox/learnpianochords/src/FlashCard.elm new file mode 100644 index 000000000000..a4917529392a --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/FlashCard.elm @@ -0,0 +1,42 @@ +module FlashCard exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Responsive +import State +import Tailwind +import Theory + + +render : + { chord : Theory.Chord + , visible : Bool + } + -> Html State.Msg +render { chord, visible } = + let + classes = + [ "bg-white" + , "fixed" + , "top-0" + , "left-0" + , "z-30" + , "w-screen" + , "h-screen" + , Tailwind.if_ visible "opacity-100" "opacity-0" + ] + in + button + [ classes |> Tailwind.use |> class ] + [ h1 + [ [ "text-center" + , "transform" + , "-rotate-90" + , Responsive.h1 + ] + |> Tailwind.use + |> class + ] + [ text (Theory.viewChord chord) ] + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Icon.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Icon.elm new file mode 100644 index 000000000000..2c8626b09293 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Icon.elm @@ -0,0 +1,44 @@ +module Icon exposing (..) + +import Svg exposing (node, svg) +import Svg.Attributes exposing (..) +import UI + + +svgColor color = + let + classes = + case color of + UI.Primary -> + [ "text-gray-500", "fill-current" ] + + UI.Secondary -> + [ "text-gray-300", "fill-current" ] + in + class <| String.join " " classes + + +cog = + svg [ class "icon-cog", viewBox "0 0 24 24", xmlLang "http://www.w3.org/2000/svg" ] + [ Svg.path + [ svgColor UI.Primary + , d "M6.8 3.45c.87-.52 1.82-.92 2.83-1.17a2.5 2.5 0 0 0 4.74 0c1.01.25 1.96.65 2.82 1.17a2.5 2.5 0 0 0 3.36 3.36c.52.86.92 1.8 1.17 2.82a2.5 2.5 0 0 0 0 4.74c-.25 1.01-.65 1.96-1.17 2.82a2.5 2.5 0 0 0-3.36 3.36c-.86.52-1.8.92-2.82 1.17a2.5 2.5 0 0 0-4.74 0c-1.01-.25-1.96-.65-2.82-1.17a2.5 2.5 0 0 0-3.36-3.36 9.94 9.94 0 0 1-1.17-2.82 2.5 2.5 0 0 0 0-4.74c.25-1.01.65-1.96 1.17-2.82a2.5 2.5 0 0 0 3.36-3.36zM12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" + , fill "red" + ] + [] + , node "circle" + [ svgColor UI.Secondary, cx "12", cy "12", r "2" ] + [] + ] + + +close = + svg [ class "icon-close", viewBox "0 0 24 24", xmlLang "http://www.w3.org/2000/svg" ] + [ Svg.path + [ svgColor UI.Primary + , d "M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z" + , fill "red" + , fillRule "evenodd" + ] + [] + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Main.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Main.elm new file mode 100644 index 000000000000..b066fb2f6f92 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Main.elm @@ -0,0 +1,44 @@ +module Main exposing (main) + +import Browser +import Html exposing (..) +import Misc +import Overview +import Practice +import Preferences +import State +import Time exposing (..) + + +subscriptions : State.Model -> Sub State.Msg +subscriptions model = + if model.isPaused then + Sub.none + + else + Sub.batch + [ Time.every (model.tempo * 2 |> Misc.bpmToMilliseconds |> toFloat) (\_ -> State.ToggleFlashCard) + , Time.every (model.tempo |> Misc.bpmToMilliseconds |> toFloat) (\_ -> State.NextChord) + ] + + +view : State.Model -> Html State.Msg +view model = + case model.view of + State.Preferences -> + Preferences.render model + + State.Practice -> + Practice.render model + + State.Overview -> + Overview.render model + + +main = + Browser.element + { init = \() -> ( State.init, Cmd.none ) + , subscriptions = subscriptions + , update = State.update + , view = view + } diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Misc.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Misc.elm new file mode 100644 index 000000000000..288d7a825f4b --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Misc.elm @@ -0,0 +1,59 @@ +module Misc exposing (..) + +import Array exposing (Array) + + +comesAfter : a -> List a -> Maybe a +comesAfter x xs = + case xs of + [] -> + Nothing + + _ :: [] -> + Nothing + + y :: z :: rest -> + if y == x then + Just z + + else + comesAfter x (z :: rest) + + +comesBefore : a -> List a -> Maybe a +comesBefore x xs = + case xs of + [] -> + Nothing + + _ :: [] -> + Nothing + + y :: z :: rest -> + if z == x then + Just y + + else + comesBefore x (z :: rest) + + +find : (a -> Bool) -> List a -> Maybe a +find pred xs = + case xs |> List.filter pred of + [] -> + Nothing + + x :: _ -> + Just x + + +{-| Return the number of milliseconds that elapse during an interval in a +`target` bpm. +-} +bpmToMilliseconds : Int -> Int +bpmToMilliseconds target = + let + msPerMinute = + 1000 * 60 + in + round (toFloat msPerMinute / toFloat target) diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Overview.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Overview.elm new file mode 100644 index 000000000000..628b52d79da9 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Overview.elm @@ -0,0 +1,122 @@ +module Overview exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Responsive +import State +import Tailwind +import UI + + +header1 : String -> Html msg +header1 copy = + h2 + [ [ "text-center" + , "pt-24" + , "pb-12" + , Responsive.h1 + ] + |> Tailwind.use + |> class + ] + [ text copy ] + + +header2 : String -> Html msg +header2 copy = + h2 + [ [ "text-center" + , "pb-10" + , Responsive.h2 + ] + |> Tailwind.use + |> class + ] + [ text copy ] + + +paragraph : String -> Html msg +paragraph copy = + p + [ [ "pb-10" + , Responsive.h3 + ] + |> Tailwind.use + |> class + ] + [ text copy ] + + +sect : { title : String, copy : List String } -> Html msg +sect { title, copy } = + section [] (header2 title :: (copy |> List.map paragraph)) + + +numberedList : List String -> Html msg +numberedList items = + ol + [ [ "list-inside" + , "list-decimal" + , Responsive.h3 + ] + |> Tailwind.use + |> class + ] + (items |> List.map (\x -> li [ [ "pb-10" ] |> Tailwind.use |> class ] [ text x ])) + + +render : State.Model -> Html State.Msg +render model = + div [ [ "container", "mx-auto" ] |> Tailwind.use |> class ] + [ header1 "Welcome to LearnPianoChords.app!" + , paragraph """ + Learn Piano Chords helps piano players master chords. + """ + , paragraph """ + Chords are the building blocks songwriters use to create + music. Whether you're a performer or songwriter, you need + to understand chords to unlock your full musical potential. + """ + , paragraph """ + I think that if practicing is enjoyable, students will + practice more. Practice doesnโt make perfect; perfect + practice makes perfect. + """ + , section [] + [ header2 "Ready to get started?" + , numberedList + [ """ + Sit down at the piano. + """ + , """ + Set the tempo at which you would like to practice. + """ + , """ + Select the key or keys in which you would like to + practice. + """ + , """ + When you are ready, close the preferences pane. We will show + you the name of a chord, and you should play that chord on + the piano. + """ + , """ + If you don't know how to play the chord, toggle the piano + viewer to see the notes. + """ + , """ + At any point while you're training, press the screen to pause + or resume your practice. + """ + ] + ] + , div [ [ "text-center", "py-20" ] |> Tailwind.use |> class ] + [ UI.simpleButton + { label = "Let's get started" + , handleClick = State.SetView State.Preferences + , color = UI.Secondary + , classes = [] + } + ] + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Piano.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Piano.elm new file mode 100644 index 000000000000..d231f1467438 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Piano.elm @@ -0,0 +1,194 @@ +module Piano exposing (render) + +import Browser +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import List.Extra +import Theory +import UI + + +type alias KeyMarkup a = + { offset : Int + , isHighlit : Bool + , note : Theory.Note + , isRootNote : Bool + } + -> Html a + + +type alias Props = + { chord : Maybe Theory.Chord + , firstNote : Theory.Note + , lastNote : Theory.Note + } + + +naturalThickness : Int +naturalThickness = + 105 + + +accidentalThickness : Int +accidentalThickness = + round (toFloat naturalThickness / 2.0) + + +{-| Convert an integer into its pixel representation for CSS. +-} +pixelate : Int -> String +pixelate x = + String.fromInt x ++ "px" + + +{-| Return the markup for either a white or a black key. +-} +pianoKey : KeyMarkup a +pianoKey { offset, isHighlit, note, isRootNote } = + let + { natColor, accColor, hiColor, rootColor } = + { natColor = "bg-white" + , accColor = "bg-black" + , hiColor = "bg-red-400" + , rootColor = "bg-red-600" + } + + sharedClasses = + [ "box-border" + , "absolute" + , "border" + , "border-black" + ] + + { keyLength, keyThickness, keyColor, offsetEdge, extraClasses } = + case Theory.keyClass note of + Theory.Natural -> + { keyLength = "w-screen" + , keyThickness = naturalThickness + , keyColor = natColor + , offsetEdge = "top" + , extraClasses = [] + } + + Theory.Accidental -> + { keyLength = "w-2/3" + , keyThickness = accidentalThickness + , keyColor = accColor + , offsetEdge = "top" + , extraClasses = [ "z-10" ] + } + in + div + [ class + (case ( isHighlit, isRootNote ) of + ( False, _ ) -> + keyColor + + ( True, True ) -> + rootColor + + ( True, False ) -> + hiColor + ) + , class keyLength + , style "height" (pixelate keyThickness) + , style offsetEdge (String.fromInt offset ++ "px") + , class <| String.join " " (List.concat [ sharedClasses, extraClasses ]) + ] + [] + + +{-| A section of the piano consisting of all twelve notes. +-} +keys : + { start : Theory.Note + , end : Theory.Note + , highlitNotes : List Theory.Note + , rootNote : Maybe Theory.Note + } + -> List (Html a) +keys { start, end, highlitNotes, rootNote } = + let + isHighlit note = + List.member note highlitNotes + + spacing prevOffset prev curr = + case ( Theory.keyClass prev, Theory.keyClass curr ) of + ( Theory.Natural, Theory.Accidental ) -> + prevOffset + naturalThickness - round (toFloat accidentalThickness / 2) + + ( Theory.Accidental, Theory.Natural ) -> + prevOffset + round (toFloat accidentalThickness / 2) + + ( Theory.Natural, Theory.Natural ) -> + prevOffset + naturalThickness + + -- This pattern should never hit. + _ -> + prevOffset + + ( _, _, notes ) = + Theory.notesFromRange start end + |> List.reverse + |> List.foldl + (\curr ( prevOffset, prev, result ) -> + case ( prevOffset, prev ) of + ( Nothing, Nothing ) -> + ( Just 0 + , Just curr + , pianoKey + { offset = 0 + , isHighlit = List.member curr highlitNotes + , note = curr + , isRootNote = + rootNote + |> Maybe.map (\x -> x == curr) + |> Maybe.withDefault False + } + :: result + ) + + ( Just po, Just p ) -> + let + offset = + spacing po p curr + in + ( Just offset + , Just curr + , pianoKey + { offset = offset + , isHighlit = List.member curr highlitNotes + , note = curr + , isRootNote = + rootNote + |> Maybe.map (\x -> x == curr) + |> Maybe.withDefault False + } + :: result + ) + + -- This pattern should never hit. + _ -> + ( Nothing, Nothing, [] ) + ) + ( Nothing, Nothing, [] ) + in + notes + + +{-| Return the HTML that renders a piano representation. +-} +render : Props -> Html a +render { chord } = + div [ style "display" "flex" ] + (keys + { start = Theory.G3 + , end = Theory.C6 + , rootNote = chord |> Maybe.map .note + , highlitNotes = + chord + |> Maybe.andThen Theory.notesForChord + |> Maybe.withDefault [] + } + ) diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Practice.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Practice.elm new file mode 100644 index 000000000000..5d87bcee501e --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Practice.elm @@ -0,0 +1,61 @@ +module Practice exposing (render) + +import FlashCard +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Icon +import Piano +import State +import Tailwind +import Theory +import UI + + +openPreferences : Html State.Msg +openPreferences = + button + [ class "w-48 h-48 absolute left-0 top-0 z-50" + , onClick (State.SetView State.Preferences) + ] + [ Icon.cog ] + + +render : State.Model -> Html State.Msg +render model = + let + ( handleClick, buttonText ) = + if model.isPaused then + ( State.Play, "Tap to practice" ) + + else + ( State.Pause, "" ) + in + div [] + [ openPreferences + , case model.selectedChord of + Just chord -> + FlashCard.render + { chord = chord + , visible = model.showFlashCard + } + + Nothing -> + -- Here I'm abusing the overlayButton component to render text + -- horizontally. I should support a UI component for this. + UI.overlayButton + { label = "Get ready..." + , handleClick = State.DoNothing + , isVisible = True + } + , UI.overlayButton + { label = buttonText + , handleClick = handleClick + , isVisible = model.isPaused + } + , Piano.render + { chord = model.selectedChord + , firstNote = model.firstNote + , lastNote = model.lastNote + } + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Preferences.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Preferences.elm new file mode 100644 index 000000000000..59e6c8234c13 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Preferences.elm @@ -0,0 +1,148 @@ +module Preferences exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Icon +import Responsive +import State +import Tailwind +import Tempo +import Theory +import UI + + +selectKey : + State.Model + -> + { relativeMajor : Theory.Key + , relativeMinor : Theory.Key + } + -> Html State.Msg +selectKey model { relativeMajor, relativeMinor } = + let + active key = + List.member key model.whitelistedKeys + + buttonLabel major minor = + Theory.viewKey major ++ ", " ++ Theory.viewKey minor + in + div [ class "flex pt-0" ] + [ UI.textToggleButton + { label = buttonLabel relativeMajor relativeMinor + , handleClick = State.ToggleKey relativeMajor + , classes = [ "flex-1" ] + , toggled = active relativeMajor + } + ] + + +inversionCheckboxes : State.Model -> Html State.Msg +inversionCheckboxes model = + div [] + [ h2 + [ [ "text-gray-500" + , "text-center" + , "pt-10" + , Responsive.h2 + ] + |> Tailwind.use + |> class + ] + [ text "Select inversions" ] + , ul + [ [ "flex", "justify-center" ] |> Tailwind.use |> class ] + (Theory.allInversions + |> List.map + (\inversion -> + li [] + [ UI.textToggleButton + { label = Theory.inversionName inversion + , handleClick = State.ToggleInversion inversion + , classes = [] + , toggled = List.member inversion model.whitelistedInversions + } + ] + ) + ) + ] + + +keyCheckboxes : State.Model -> Html State.Msg +keyCheckboxes model = + let + majorKey pitchClass = + { pitchClass = pitchClass, mode = Theory.MajorMode } + + minorKey pitchClass = + { pitchClass = pitchClass, mode = Theory.MinorMode } + + circleOfFifths = + [ ( Theory.C, Theory.A ) + , ( Theory.G, Theory.E ) + , ( Theory.D, Theory.B ) + , ( Theory.A, Theory.F_sharp ) + , ( Theory.E, Theory.C_sharp ) + , ( Theory.B, Theory.G_sharp ) + , ( Theory.F_sharp, Theory.D_sharp ) + , ( Theory.C_sharp, Theory.A_sharp ) + , ( Theory.G_sharp, Theory.F ) + , ( Theory.D_sharp, Theory.C ) + , ( Theory.A_sharp, Theory.G ) + , ( Theory.F, Theory.D ) + ] + in + div [] + [ h2 + [ [ "text-gray-500" + , "text-center" + , "pt-10" + , Responsive.h2 + ] + |> Tailwind.use + |> class + ] + [ text "Select keys" ] + , ul [] + (circleOfFifths + |> List.map + (\( major, minor ) -> + selectKey model + { relativeMajor = majorKey major + , relativeMinor = minorKey minor + } + ) + ) + ] + + +closePreferences : Html State.Msg +closePreferences = + button + [ [ "w-48" + , "lg:w-32" + , "h-48" + , "lg:h-32" + , "absolute" + , "right-0" + , "top-0" + , "z-10" + ] + |> Tailwind.use + |> class + , onClick (State.SetView State.Practice) + ] + [ Icon.close ] + + +render : State.Model -> Html State.Msg +render model = + div [ class "pt-10 pb-20 px-10" ] + [ closePreferences + , Tempo.render + { tempo = model.tempo + , handleInput = State.SetTempo + } + , inversionCheckboxes model + , keyCheckboxes model + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Responsive.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Responsive.elm new file mode 100644 index 000000000000..5d97161df6a8 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Responsive.elm @@ -0,0 +1,19 @@ +module Responsive exposing (..) + +{-| Returns a string containing all of the Tailwind selectors we use to size +h2-sized elements across various devices. -} +h1 : String +h1 = + "text-6xl lg:text-4xl" + +{-| Returns a string containing all of the Tailwind selectors we use to size +h2-sized elements across various devices. -} +h2 : String +h2 = + "text-5xl lg:text-3xl" + +{-| Returns a string containing all of the Tailwind selectors we use to size +h3-sized elements across various devices. -} +h3 : String +h3 = + "text-4xl lg:text-2xl" diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/State.elm b/users/wpcarro/website/sandbox/learnpianochords/src/State.elm new file mode 100644 index 000000000000..678fb0f9aa79 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/State.elm @@ -0,0 +1,179 @@ +module State exposing (..) + +import Random +import Random.List +import Theory + + +type Msg + = NextChord + | NewChord Theory.Chord + | Play + | Pause + | SetTempo String + | ToggleInversion Theory.ChordInversion + | ToggleKey Theory.Key + | DoNothing + | SetView View + | ToggleFlashCard + + +type View + = Preferences + | Practice + | Overview + + +type alias Model = + { whitelistedChords : List Theory.Chord + , whitelistedChordTypes : List Theory.ChordType + , whitelistedInversions : List Theory.ChordInversion + , whitelistedPitchClasses : List Theory.PitchClass + , whitelistedKeys : List Theory.Key + , selectedChord : Maybe Theory.Chord + , isPaused : Bool + , tempo : Int + , firstNote : Theory.Note + , lastNote : Theory.Note + , view : View + , showFlashCard : Bool + } + + +{-| The initial state for the application. +-} +init : Model +init = + let + ( firstNote, lastNote ) = + ( Theory.C3, Theory.C6 ) + + inversions = + [ Theory.Root ] + + chordTypes = + Theory.allChordTypes + + pitchClasses = + Theory.allPitchClasses + + keys = + [ { pitchClass = Theory.C, mode = Theory.MajorMode } ] + in + { whitelistedChords = + keys + |> List.concatMap Theory.chordsForKey + |> List.filter (\chord -> List.member chord.chordInversion inversions) + , whitelistedChordTypes = chordTypes + , whitelistedInversions = inversions + , whitelistedPitchClasses = pitchClasses + , whitelistedKeys = keys + , selectedChord = Nothing + , isPaused = True + , tempo = 10 + , firstNote = firstNote + , lastNote = lastNote + , view = Overview + , showFlashCard = True + } + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + DoNothing -> + ( model, Cmd.none ) + + SetView x -> + ( { model + | view = x + , isPaused = True + } + , Cmd.none + ) + + NewChord chord -> + ( { model | selectedChord = Just chord } + , Cmd.none + ) + + NextChord -> + ( model + , Random.generate + (\x -> + case x of + ( Just chord, _ ) -> + NewChord chord + + ( Nothing, _ ) -> + DoNothing + ) + (Random.List.choose model.whitelistedChords) + ) + + Play -> + ( { model | isPaused = False } + , Cmd.none + ) + + Pause -> + ( { model | isPaused = True } + , Cmd.none + ) + + ToggleInversion inversion -> + let + inversions = + if List.member inversion model.whitelistedInversions then + List.filter ((/=) inversion) model.whitelistedInversions + + else + inversion :: model.whitelistedInversions + in + ( { model + | whitelistedInversions = inversions + , whitelistedChords = + model.whitelistedKeys + |> List.concatMap Theory.chordsForKey + |> List.filter (\chord -> List.member chord.chordInversion inversions) + } + , Cmd.none + ) + + ToggleKey key -> + let + keys = + if List.member key model.whitelistedKeys then + List.filter ((/=) key) model.whitelistedKeys + + else + key :: model.whitelistedKeys + in + ( { model + | whitelistedKeys = keys + , whitelistedChords = + keys + |> List.concatMap Theory.chordsForKey + |> List.filter (\chord -> List.member chord.chordInversion model.whitelistedInversions) + , selectedChord = Nothing + } + , Cmd.none + ) + + SetTempo tempo -> + ( { model + | tempo = + case String.toInt tempo of + Just x -> + x + + Nothing -> + model.tempo + } + , Cmd.none + ) + + ToggleFlashCard -> + ( { model | showFlashCard = not model.showFlashCard }, Cmd.none ) diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Tailwind.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Tailwind.elm new file mode 100644 index 000000000000..57d419db5a82 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Tailwind.elm @@ -0,0 +1,29 @@ +module Tailwind exposing (..) + +{-| Functions to make Tailwind development in Elm even more pleasant. +-} + + +{-| Conditionally use `class` selection when `condition` is true. +-} +when : Bool -> String -> String +when condition class = + if condition then + class + + else + "" + + +if_ : Bool -> String -> String -> String +if_ condition whenTrue whenFalse = + if condition then + whenTrue + + else + whenFalse + + +use : List String -> String +use styles = + String.join " " styles diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Tempo.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Tempo.elm new file mode 100644 index 000000000000..041313614f53 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Tempo.elm @@ -0,0 +1,33 @@ +module Tempo exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Responsive +import Tailwind +import UI + + +type alias Props msg = + { tempo : Int + , handleInput : String -> msg + } + + +render : Props msg -> Html msg +render { tempo, handleInput } = + div [ class "text-center" ] + [ p + [ [ "py-10" + , Responsive.h2 + ] + |> Tailwind.use + |> class + ] + [ text (String.fromInt tempo ++ " BPM") ] + , UI.textField + { placeholderText = "Set tempo..." + , handleInput = handleInput + , classes = [] + } + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/Theory.elm b/users/wpcarro/website/sandbox/learnpianochords/src/Theory.elm new file mode 100644 index 000000000000..7f54832c97a0 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/Theory.elm @@ -0,0 +1,1100 @@ +module Theory exposing (..) + +import Array exposing (Array) +import Dict exposing (Dict) +import List.Extra +import Maybe.Extra +import Misc + + +{-| Notes are the individuals sounds that we use to create music. Think: "do re +mi fa so la ti do". + +Note: Technically a "C-sharp" is also a "D-flat", but I will model accidentals +(i.e. sharps and flats) as sharps and represent the ambiguity when I render the +underlying state of the application. + +Note: There are "notes" like A, B, D-flat, and then there are notes like "middle +C", also denoted in scientific pitch notation as C4. I'm unsure of what to call +each of these, and my application does not model scientific pitch notation yet, +so these non-scientific pitch denote values are "notes" for now. + +-} +type Note + = C1 + | C_sharp1 + | D1 + | D_sharp1 + | E1 + | F1 + | F_sharp1 + | G1 + | G_sharp1 + | A1 + | A_sharp1 + | B1 + | C2 + | C_sharp2 + | D2 + | D_sharp2 + | E2 + | F2 + | F_sharp2 + | G2 + | G_sharp2 + | A2 + | A_sharp2 + | B2 + | C3 + | C_sharp3 + | D3 + | D_sharp3 + | E3 + | F3 + | F_sharp3 + | G3 + | G_sharp3 + | A3 + | A_sharp3 + | B3 + | C4 + | C_sharp4 + | D4 + | D_sharp4 + | E4 + | F4 + | F_sharp4 + | G4 + | G_sharp4 + | A4 + | A_sharp4 + | B4 + | C5 + | C_sharp5 + | D5 + | D_sharp5 + | E5 + | F5 + | F_sharp5 + | G5 + | G_sharp5 + | A5 + | A_sharp5 + | B5 + | C6 + | C_sharp6 + | D6 + | D_sharp6 + | E6 + | F6 + | F_sharp6 + | G6 + | G_sharp6 + | A6 + | A_sharp6 + | B6 + | C7 + | C_sharp7 + | D7 + | D_sharp7 + | E7 + | F7 + | F_sharp7 + | G7 + | G_sharp7 + | A7 + | A_sharp7 + | B7 + | C8 + + +{-| I alluded to this concept in the Note type's documentation. These are the +letters of notes. For instance C2, C3, C4 are all instances of C. +-} +type PitchClass + = C + | C_sharp + | D + | D_sharp + | E + | F + | F_sharp + | G + | G_sharp + | A + | A_sharp + | B + + +{-| Encode whether you are traversing "up" or "down" intervals +-} +type StepDirection + = Up + | Down + + +{-| One can measure the difference between between notes using intervals. +-} +type Interval + = Half + | NHalves Int + | Whole + | MajorThird + | MinorThird + | PerfectFifth + | AugmentedFifth + | DiminishedFifth + | MajorSeventh + | DominantSeventh + + +{-| Add direction to a distance on the piano. +-} +type alias IntervalVector = + { interval : Interval + , direction : StepDirection + } + + +{-| A bundle of notes which are usually, but not necessarily harmonious. +-} +type alias Chord = + { note : Note + , chordType : ChordType + , chordInversion : ChordInversion + } + + +{-| Many possible chords exist. This type encodes the possibilities. I am +tempted to model these in a more "DRY" way, but I worry that this abstraction +may cause more problems than it solves. +-} +type ChordType + = Major + | Sus2 + | Sus4 + | Major7 + | MajorDominant7 + | Minor + | MinorMajor7 + | MinorDominant7 + | Augmented + | AugmentedDominant7 + | Diminished + | DiminishedDominant7 + | DiminishedMajor7 + + +{-| On a piano, a triad can be played three ways. As a rule-of-thumb, The number +of ways a pianist can play a chord is equal to the number of notes in the chord +itself. +-} +type ChordInversion + = Root + | First + | Second + + +{-| Whether a given note is a white key or a black key. +-} +type KeyClass + = Natural + | Accidental + + +{-| Songs are written in one or more keys, which define the notes and therefore +chords that harmonize with one another. +-} +type alias Key = + { pitchClass : PitchClass + , mode : Mode + } + + +{-| We create "scales" by enumerating the notes of a given key. These keys are +defined by the "tonic" note and the "mode". I thought about including Ionian, +Dorian, Phrygian, etc., but in the I would like to avoid over-abstracting this +early on, so I'm going to err on the side of overly concrete until I have a +better idea of the extent of this project. +-} +type Mode + = BluesMode + | MajorMode + | MinorMode + + +type alias NoteMetadata = + { note : Note + , label : String + , pitchClass : PitchClass + , natural : Bool + } + + +{-| An integer representing which note in a given scale to play. +-} +type alias ScaleDegree = + Int + + +{-| Returns the Note in the cental octave of the piano for a given +PitchClass. For example, C4 -- or "middle C" -- for C. +-} +noteInCentralOctave : PitchClass -> Note +noteInCentralOctave pitchClass = + case pitchClass of + C -> + C4 + + C_sharp -> + C_sharp4 + + D -> + D4 + + D_sharp -> + D_sharp4 + + E -> + E4 + + F -> + F4 + + F_sharp -> + F_sharp4 + + G -> + G4 + + G_sharp -> + G_sharp4 + + A -> + A4 + + A_sharp -> + A_sharp4 + + B -> + B4 + + +{-| Return the human-readable version of a chord inversion. +-} +inversionName : ChordInversion -> String +inversionName inversion = + case inversion of + Root -> + "Root" + + First -> + "First" + + Second -> + "Second" + + +{-| Return the human-readable version of a chord type. +-} +chordTypeName : ChordType -> String +chordTypeName chordType = + case chordType of + Major -> + "major" + + Sus2 -> + "suspended 2" + + Sus4 -> + "suspended 4" + + Major7 -> + "major 7th" + + MajorDominant7 -> + "major dominant 7th" + + Minor -> + "minor" + + MinorMajor7 -> + "minor major 7th" + + MinorDominant7 -> + "minor dominant 7th" + + Augmented -> + "augmented" + + AugmentedDominant7 -> + "augmented dominant 7th" + + Diminished -> + "diminished" + + DiminishedDominant7 -> + "diminished dominant 7th" + + DiminishedMajor7 -> + "diminished major 7th" + + +{-| Return the note that is one half step away from `note` in the direction, +`dir`. +In the case of stepping up or down from the end of the piano, this returns a +Maybe. +-} +halfStep : StepDirection -> Note -> Maybe Note +halfStep dir note = + let + everyNote = + notesFromRange C2 C8 + in + case dir of + Up -> + Misc.comesAfter note everyNote + + Down -> + Misc.comesBefore note everyNote + + +{-| Return a list of steps to take away from the root note to return back to the +root note for a given mode. +-} +intervalsForMode : Mode -> List IntervalVector +intervalsForMode mode = + let + up x = + { direction = Up, interval = x } + + down x = + { direction = Down, interval = x } + in + case mode of + MajorMode -> + List.map up [ Whole, Whole, Half, Whole, Whole, Whole ] + + MinorMode -> + List.map up [ Whole, Half, Whole, Whole, Half, Whole ] + + BluesMode -> + List.map up [ MinorThird, Whole, Half, Half, MinorThird ] + + +{-| Return a list of the intervals that a chord. Each interval measures +the distance away from the root-note of the chord. +-} +intervalsForChordType : ChordType -> ChordInversion -> List IntervalVector +intervalsForChordType chordType chordInversion = + let + up x = + { direction = Up, interval = x } + + down x = + { direction = Down, interval = x } + in + case ( chordType, chordInversion ) of + -- Major + ( Major, Root ) -> + [ up MajorThird, up PerfectFifth ] + + ( Major, First ) -> + [ down (NHalves 5), down (NHalves 8) ] + + ( Major, Second ) -> + [ down (NHalves 5), up MajorThird ] + + -- Sus2 + ( Sus2, Root ) -> + [ up Whole, up PerfectFifth ] + + ( Sus2, First ) -> + [ down (NHalves 10), down (NHalves 5) ] + + ( Sus2, Second ) -> + [ down (NHalves 5), up Whole ] + + -- Sus4 + ( Sus4, Root ) -> + [ up (NHalves 5), up PerfectFifth ] + + ( Sus4, First ) -> + [ down (NHalves 7), down (NHalves 5) ] + + ( Sus4, Second ) -> + [ down (NHalves 5), up (NHalves 5) ] + + -- Major7 + ( Major7, Root ) -> + [ up MajorThird, up PerfectFifth, up MajorSeventh ] + + ( Major7, First ) -> + down Half :: intervalsForChordType Major chordInversion + + ( Major7, Second ) -> + down Half :: intervalsForChordType Major chordInversion + + -- MajorDominant7 + ( MajorDominant7, Root ) -> + up DominantSeventh :: intervalsForChordType Major chordInversion + + ( MajorDominant7, First ) -> + down Whole :: intervalsForChordType Major chordInversion + + ( MajorDominant7, Second ) -> + down Whole :: intervalsForChordType Major chordInversion + + -- Minor + ( Minor, Root ) -> + [ up MinorThird, up PerfectFifth ] + + ( Minor, First ) -> + [ down (NHalves 5), down (NHalves 9) ] + + ( Minor, Second ) -> + [ down (NHalves 5), up MinorThird ] + + -- MinorMajor7 + ( MinorMajor7, Root ) -> + up MajorSeventh :: intervalsForChordType Minor chordInversion + + ( MinorMajor7, First ) -> + down Half :: intervalsForChordType Minor chordInversion + + ( MinorMajor7, Second ) -> + down Half :: intervalsForChordType Minor chordInversion + + -- MinorDominant7 + ( MinorDominant7, Root ) -> + up DominantSeventh :: intervalsForChordType Minor chordInversion + + ( MinorDominant7, First ) -> + down Whole :: intervalsForChordType Minor chordInversion + + ( MinorDominant7, Second ) -> + down Whole :: intervalsForChordType Minor chordInversion + + -- Augmented + ( Augmented, Root ) -> + [ up MajorThird, up AugmentedFifth ] + + ( Augmented, First ) -> + [ down (NHalves 8), down (NHalves 4) ] + + ( Augmented, Second ) -> + [ down (NHalves 4), up MajorThird ] + + -- AugmentedDominant7 + ( AugmentedDominant7, Root ) -> + up DominantSeventh :: intervalsForChordType Augmented chordInversion + + ( AugmentedDominant7, First ) -> + down Whole :: intervalsForChordType Augmented chordInversion + + ( AugmentedDominant7, Second ) -> + down Whole :: intervalsForChordType Augmented chordInversion + + -- Diminished + ( Diminished, Root ) -> + [ up MinorThird, up DiminishedFifth ] + + ( Diminished, First ) -> + [ down (NHalves 6), down (NHalves 9) ] + + ( Diminished, Second ) -> + [ down (NHalves 6), up MinorThird ] + + -- DiminishedDominant7 + ( DiminishedDominant7, Root ) -> + up DominantSeventh :: intervalsForChordType Diminished chordInversion + + ( DiminishedDominant7, First ) -> + down Whole :: intervalsForChordType Diminished chordInversion + + ( DiminishedDominant7, Second ) -> + down Whole :: intervalsForChordType Diminished chordInversion + + -- DiminishedMajor7 + ( DiminishedMajor7, Root ) -> + up MajorSeventh :: intervalsForChordType Diminished chordInversion + + ( DiminishedMajor7, First ) -> + down Half :: intervalsForChordType Diminished chordInversion + + ( DiminishedMajor7, Second ) -> + down Half :: intervalsForChordType Diminished chordInversion + + +{-| Return the note in the direction, `dir`, away from `note` `s` intervals +-} +step : IntervalVector -> Note -> Maybe Note +step { direction, interval } note = + let + doStep int = + step { direction = direction, interval = int } + in + case interval of + Half -> + halfStep direction note + + NHalves n -> + List.repeat n + { direction = direction + , interval = Half + } + |> (\x -> walkNotes x note) + |> Maybe.andThen (List.reverse >> List.head) + + Whole -> + note + |> doStep Half + |> Maybe.andThen (doStep Half) + + MinorThird -> + note + |> doStep Whole + |> Maybe.andThen (doStep Half) + + MajorThird -> + note + |> doStep Whole + |> Maybe.andThen (doStep Whole) + + PerfectFifth -> + note + |> doStep MajorThird + |> Maybe.andThen (doStep MinorThird) + + AugmentedFifth -> + note + |> doStep PerfectFifth + |> Maybe.andThen (doStep Half) + + DiminishedFifth -> + note + |> doStep MajorThird + |> Maybe.andThen (doStep Whole) + + MajorSeventh -> + note + |> doStep PerfectFifth + |> Maybe.andThen (doStep MajorThird) + + DominantSeventh -> + note + |> doStep PerfectFifth + |> Maybe.andThen (doStep MinorThird) + + +{-| Returns a list of all of the notes away from a give `note`. + + - The 0th element is applied to `note`. + - The 1st element is applied to the result of the previous operation. + - The 2nd element is applied to the result of the previous operation. + - and so on...until all of the `steps` are exhausted. + +In the case where applying any of the steps would result in running off of +either edge of the piano, this function returns a Nothing. + +-} +walkNotes : List IntervalVector -> Note -> Maybe (List Note) +walkNotes steps note = + doWalkNotes steps note [] |> Maybe.map List.reverse + + +{-| Recursive helper for `walkNotes`. +-} +doWalkNotes : List IntervalVector -> Note -> List Note -> Maybe (List Note) +doWalkNotes steps note result = + case steps of + [] -> + Just (note :: result) + + s :: rest -> + case step s note of + Just x -> + doWalkNotes rest x (note :: result) + + Nothing -> + Nothing + + +{-| Return the KeyClass for a given `note`. +-} +keyClass : Note -> KeyClass +keyClass note = + if isNatural note then + Natural + + else + Accidental + + +{-| Return the PitchClass for a given note. +-} +classifyNote : Note -> PitchClass +classifyNote note = + note |> getNoteMetadata |> .pitchClass + + +{-| Return a list of the notes that comprise a `chord` +-} +notesForChord : Chord -> Maybe (List Note) +notesForChord { note, chordType, chordInversion } = + intervalsForChordType chordType chordInversion + |> List.map (\interval -> step interval note) + |> Maybe.Extra.combine + |> Maybe.map (\notes -> note :: notes) + + +{-| Return the scale for a given `key`. +-} +notesForKey : Key -> List Note +notesForKey { pitchClass, mode } = + let + origin = + noteInCentralOctave pitchClass + in + case walkNotes (intervalsForMode mode) origin of + -- We should never hit the Nothing case here. + Nothing -> + [] + + Just scale -> + scale + + +{-| Return true if `note` is a black key. +-} +isAccidental : Note -> Bool +isAccidental note = + note |> isNatural |> not + + +{-| Return true if `note` is a white key. +-} +isNatural : Note -> Bool +isNatural note = + note |> getNoteMetadata |> .natural + + +{-| Return a list of all of the notes that we know about. +Only return the notes within the range `start` and `end`. +-} +notesFromRange : Note -> Note -> List Note +notesFromRange start end = + noteMetadata + |> Array.toList + |> List.map .note + |> List.Extra.dropWhile ((/=) start) + |> List.Extra.takeWhile ((/=) end) + + +{-| Return a list of all of the chord inversions about which we know. +-} +allInversions : List ChordInversion +allInversions = + [ Root, First, Second ] + + +{-| Return a list of all of the chord types about which we know. +-} +allChordTypes : List ChordType +allChordTypes = + [ Major + , Sus2 + , Sus4 + , Major7 + , MajorDominant7 + , Minor + , MinorMajor7 + , MinorDominant7 + , Augmented + , AugmentedDominant7 + , Diminished + , DiminishedDominant7 + , DiminishedMajor7 + ] + + +{-| Return a list of all of the key modes about which we know. +-} +allModes : List Mode +allModes = + [ MajorMode, MinorMode, BluesMode ] + + +{-| Return a list of all of the keys about which we know. +-} +allKeys : List Key +allKeys = + allPitchClasses + |> List.Extra.andThen + (\pitchClass -> + allModes + |> List.Extra.andThen + (\mode -> + [ { pitchClass = pitchClass + , mode = mode + } + ] + ) + ) + + +{-| Return an array of every note on a piano. +Note: Currently this piano has 85 keys, but modern pianos have 88 keys. I would +prefer to have 88 keys, but it's not urgent. +-} +noteMetadata : Array NoteMetadata +noteMetadata = + Array.fromList + [ { note = A1, label = "A1", pitchClass = A, natural = True } + , { note = A_sharp1, label = "Aโฏ/Bโญ1", pitchClass = A_sharp, natural = False } + , { note = B1, label = "B1", pitchClass = B, natural = True } + , { note = C1, label = "C1", pitchClass = C, natural = True } + , { note = C_sharp1, label = "Cโฏ/Dโญ1", pitchClass = C_sharp, natural = False } + , { note = D1, label = "D1", pitchClass = D, natural = True } + , { note = D_sharp1, label = "Dโฏ/Eโญ1", pitchClass = D_sharp, natural = False } + , { note = E1, label = "E1", pitchClass = E, natural = True } + , { note = F1, label = "F1", pitchClass = F, natural = True } + , { note = F_sharp1, label = "Fโฏ/Gโญ1", pitchClass = F_sharp, natural = False } + , { note = G1, label = "G1", pitchClass = G, natural = True } + , { note = G_sharp1, label = "Gโฏ/Aโญ1", pitchClass = G_sharp, natural = False } + , { note = A2, label = "A2", pitchClass = A, natural = True } + , { note = A_sharp2, label = "Aโฏ/Bโญ2", pitchClass = A_sharp, natural = False } + , { note = B2, label = "B2", pitchClass = B, natural = True } + , { note = C2, label = "C2", pitchClass = C, natural = True } + , { note = C_sharp2, label = "Cโฏ/Dโญ2", pitchClass = C_sharp, natural = False } + , { note = D2, label = "D2", pitchClass = D, natural = True } + , { note = D_sharp2, label = "Dโฏ/Eโญ2", pitchClass = D_sharp, natural = False } + , { note = E2, label = "E2", pitchClass = E, natural = True } + , { note = F2, label = "F2", pitchClass = F, natural = True } + , { note = F_sharp2, label = "Fโฏ/Gโญ2", pitchClass = F_sharp, natural = False } + , { note = G2, label = "G2", pitchClass = G, natural = True } + , { note = G_sharp2, label = "Gโฏ/Aโญ2", pitchClass = G_sharp, natural = False } + , { note = A3, label = "A3", pitchClass = A, natural = True } + , { note = A_sharp3, label = "Aโฏ/Bโญ3", pitchClass = A_sharp, natural = False } + , { note = B3, label = "B3", pitchClass = B, natural = True } + , { note = C3, label = "C3", pitchClass = C, natural = True } + , { note = C_sharp3, label = "Cโฏ/Dโญ3", pitchClass = C_sharp, natural = False } + , { note = D3, label = "D3", pitchClass = D, natural = True } + , { note = D_sharp3, label = "Dโฏ/Eโญ3", pitchClass = D_sharp, natural = False } + , { note = E3, label = "E3", pitchClass = E, natural = True } + , { note = F3, label = "F3", pitchClass = F, natural = True } + , { note = F_sharp3, label = "Fโฏ/Gโญ3", pitchClass = F_sharp, natural = False } + , { note = G3, label = "G3", pitchClass = G, natural = True } + , { note = G_sharp3, label = "Gโฏ/Aโญ3", pitchClass = G_sharp, natural = False } + , { note = A4, label = "A4", pitchClass = A, natural = True } + , { note = A_sharp4, label = "Aโฏ/Bโญ4", pitchClass = A_sharp, natural = False } + , { note = B4, label = "B4", pitchClass = B, natural = True } + , { note = C4, label = "C4", pitchClass = C, natural = True } + , { note = C_sharp4, label = "Cโฏ/Dโญ4", pitchClass = C_sharp, natural = False } + , { note = D4, label = "D4", pitchClass = D, natural = True } + , { note = D_sharp4, label = "Dโฏ/Eโญ4", pitchClass = D_sharp, natural = False } + , { note = E4, label = "E4", pitchClass = E, natural = True } + , { note = F4, label = "F4", pitchClass = F, natural = True } + , { note = F_sharp4, label = "Fโฏ/Gโญ4", pitchClass = F_sharp, natural = False } + , { note = G4, label = "G4", pitchClass = G, natural = True } + , { note = G_sharp4, label = "Gโฏ/Aโญ4", pitchClass = G_sharp, natural = False } + , { note = A5, label = "A5", pitchClass = A, natural = True } + , { note = A_sharp5, label = "Aโฏ/Bโญ5", pitchClass = A_sharp, natural = False } + , { note = B5, label = "B5", pitchClass = B, natural = True } + , { note = C5, label = "C5", pitchClass = C, natural = True } + , { note = C_sharp5, label = "Cโฏ/Dโญ5", pitchClass = C_sharp, natural = False } + , { note = D5, label = "D5", pitchClass = D, natural = True } + , { note = D_sharp5, label = "Dโฏ/Eโญ5", pitchClass = D_sharp, natural = False } + , { note = E5, label = "E5", pitchClass = E, natural = True } + , { note = F5, label = "F5", pitchClass = F, natural = True } + , { note = F_sharp5, label = "Fโฏ/Gโญ5", pitchClass = F_sharp, natural = False } + , { note = G5, label = "G5", pitchClass = G, natural = True } + , { note = G_sharp5, label = "Gโฏ/Aโญ5", pitchClass = G_sharp, natural = False } + , { note = A6, label = "A6", pitchClass = A, natural = True } + , { note = A_sharp6, label = "Aโฏ/Bโญ6", pitchClass = A_sharp, natural = False } + , { note = B6, label = "B6", pitchClass = B, natural = True } + , { note = C6, label = "C6", pitchClass = C, natural = True } + , { note = C_sharp6, label = "Cโฏ/Dโญ6", pitchClass = C_sharp, natural = False } + , { note = D6, label = "D6", pitchClass = D, natural = True } + , { note = D_sharp6, label = "Dโฏ/Eโญ6", pitchClass = D_sharp, natural = False } + , { note = E6, label = "E6", pitchClass = E, natural = True } + , { note = F6, label = "F6", pitchClass = F, natural = True } + , { note = F_sharp6, label = "Fโฏ/Gโญ6", pitchClass = F_sharp, natural = False } + , { note = G6, label = "G6", pitchClass = G, natural = True } + , { note = G_sharp6, label = "Gโฏ/Aโญ6", pitchClass = G_sharp, natural = False } + , { note = A7, label = "A7", pitchClass = A, natural = True } + , { note = A_sharp7, label = "Aโฏ/Bโญ7", pitchClass = A_sharp, natural = False } + , { note = B7, label = "B7", pitchClass = B, natural = True } + , { note = C7, label = "C7", pitchClass = C, natural = True } + , { note = C_sharp7, label = "Cโฏ/Dโญ7", pitchClass = C_sharp, natural = False } + , { note = D7, label = "D7", pitchClass = D, natural = True } + , { note = D_sharp7, label = "Dโฏ/Eโญ7", pitchClass = D_sharp, natural = False } + , { note = E7, label = "E7", pitchClass = E, natural = True } + , { note = F7, label = "F7", pitchClass = F, natural = True } + , { note = F_sharp7, label = "Fโฏ/Gโญ7", pitchClass = F_sharp, natural = False } + , { note = G7, label = "G7", pitchClass = G, natural = True } + , { note = G_sharp7, label = "Gโฏ/Aโญ7", pitchClass = G_sharp, natural = False } + , { note = C8, label = "C8", pitchClass = C, natural = True } + ] + + +{-| Mapping of note data to commonly needed metadata for that note. +-} +getNoteMetadata : Note -> NoteMetadata +getNoteMetadata note = + case Array.get (noteAsNumber note) noteMetadata of + Just metadata -> + metadata + + -- This case should never hit, so we just return C1 to appease the + -- compiler. + Nothing -> + getNoteMetadata C1 + + +{-| Return the numeric representation of `note` to ues when comparing two +notes. +-} +noteAsNumber : Note -> Int +noteAsNumber note = + let + result = + noteMetadata + |> Array.toList + |> List.indexedMap Tuple.pair + |> Misc.find (\( _, x ) -> x.note == note) + in + case result of + Nothing -> + 0 + + Just ( i, _ ) -> + i + + +{-| Return true if all of the notes that comprise `chord` can be played on a +piano whose keys begin at `start` and end at `end`. +-} +chordWithinRange : Note -> Note -> Chord -> Bool +chordWithinRange start end chord = + case notesForChord chord of + Just notes -> + let + nums = + List.map noteAsNumber notes + + lo = + List.minimum nums |> Maybe.withDefault (noteAsNumber start) + + hi = + List.maximum nums |> Maybe.withDefault (noteAsNumber end) + in + lo >= noteAsNumber start && hi < noteAsNumber end + + Nothing -> + False + + +{-| Return a list of all of the pitch classes that we know about. +-} +allPitchClasses : List PitchClass +allPitchClasses = + [ C + , C_sharp + , D + , D_sharp + , E + , F + , F_sharp + , G + , G_sharp + , A + , A_sharp + , B + ] + + +{-| Return a list of all of the chords that we know about. +Only create chords from the range of notes delimited by the range `start` and +`end`. +-} +allChords : + { start : Note + , end : Note + , inversions : List ChordInversion + , chordTypes : List ChordType + , pitchClasses : List PitchClass + } + -> List Chord +allChords { start, end, inversions, chordTypes, pitchClasses } = + let + notes = + notesFromRange start end + |> List.filter (\note -> List.member (classifyNote note) pitchClasses) + in + notes + |> List.Extra.andThen + (\note -> + chordTypes + |> List.Extra.andThen + (\chordType -> + inversions + |> List.Extra.andThen + (\inversion -> + [ { note = note + , chordType = chordType + , chordInversion = inversion + } + ] + ) + ) + ) + |> List.filter (chordWithinRange start end) + + +{-| Return a human-readable format of `note`. +-} +viewNote : Note -> String +viewNote note = + note |> getNoteMetadata |> .label + + +{-| Return a human-readable format of `chord`. +-} +viewChord : Chord -> String +viewChord { note, chordType, chordInversion } = + viewPitchClass (classifyNote note) ++ " " ++ chordTypeName chordType ++ " " ++ inversionName chordInversion ++ " position" + + +{-| Return a human-readable format of `pitchClass`. +-} +viewPitchClass : PitchClass -> String +viewPitchClass pitchClass = + case pitchClass of + C -> + "C" + + C_sharp -> + "Cโฏ/Dโญ" + + D -> + "D" + + D_sharp -> + "Dโฏ/Eโญ" + + E -> + "E" + + F -> + "F" + + F_sharp -> + "Fโฏ/Gโญ" + + G -> + "G" + + G_sharp -> + "Gโฏ/Aโญ" + + A -> + "A" + + A_sharp -> + "Aโฏ/Bโญ" + + B -> + "B" + + +viewMode : Mode -> String +viewMode mode = + case mode of + MajorMode -> + "major" + + MinorMode -> + "minor" + + BluesMode -> + "blues" + + +{-| Return the human-readable format of `key`. +-} +viewKey : Key -> String +viewKey { pitchClass, mode } = + viewPitchClass pitchClass ++ " " ++ viewMode mode + + +{-| Returns a pairing of a scale-degree to the type of chord at that scale +degree. +-} +practiceChordsForMode : Mode -> Dict ScaleDegree ChordType +practiceChordsForMode mode = + case mode of + MajorMode -> + Dict.fromList + [ ( 1, Major ) + , ( 2, Minor ) + , ( 3, Minor ) + , ( 4, Major ) + , ( 5, Major ) + , ( 6, Minor ) + , ( 7, Diminished ) + ] + + MinorMode -> + Dict.fromList + [ ( 1, Minor ) + , ( 2, Diminished ) + , ( 3, Major ) + , ( 4, Minor ) + , ( 5, Minor ) + , ( 6, Major ) + , ( 7, Major ) + ] + + BluesMode -> + Dict.fromList + [ ( 1, MajorDominant7 ) + + -- While many refer to the blues progression as a I-IV-V, the IV + -- chord is really a MajorDominant7 made from the third scale + -- degree. + , ( 3, MajorDominant7 ) + , ( 5, MajorDominant7 ) + ] + + +{-| Returns a list of chords for a particular `key`. +-} +chordsForKey : Key -> List Chord +chordsForKey key = + let + chords = + practiceChordsForMode key.mode + in + notesForKey key + |> List.indexedMap + (\i note -> + case Dict.get (i + 1) chords of + Nothing -> + Nothing + + Just chordType -> + Just + (allInversions + |> List.Extra.andThen + (\inversion -> + [ { note = note + , chordType = chordType + , chordInversion = inversion + } + ] + ) + ) + ) + |> Maybe.Extra.values + |> List.concat diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/UI.elm b/users/wpcarro/website/sandbox/learnpianochords/src/UI.elm new file mode 100644 index 000000000000..a6876c4f8a0d --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/UI.elm @@ -0,0 +1,159 @@ +module UI exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Responsive +import Tailwind + + +type Color + = Primary + | Secondary + + +bgForColor : Color -> String +bgForColor color = + case color of + Primary -> + "bg-gray-600" + + Secondary -> + "bg-gray-300" + + +textForColor : Color -> String +textForColor color = + case color of + Primary -> + "text-white" + + Secondary -> + "text-black" + + +simpleButton : + { label : String + , handleClick : msg + , color : Color + , classes : List String + } + -> Html msg +simpleButton { label, handleClick, color, classes } = + let + buttonClasses = + [ bgForColor color + , textForColor color + , "py-10" + , "lg:py-6" + , "px-20" + , "lg:px-12" + , "rounded-lg" + , Responsive.h2 + ] + in + button + [ class (Tailwind.use <| List.concat [ buttonClasses, classes ]) + , onClick handleClick + ] + [ text label ] + + +textToggleButton : + { label : String + , handleClick : msg + , classes : List String + , toggled : Bool + } + -> Html msg +textToggleButton { label, toggled, handleClick, classes } = + let + ( textColor, textTreatment ) = + if toggled then + ( "text-red-600", "underline" ) + + else + ( "text-black", "no-underline" ) + + buttonClasses = + [ textColor + , textTreatment + , "py-8" + , "lg:py-5" + , "px-10" + , "lg:px-6" + , Responsive.h2 + ] + in + button + [ class (Tailwind.use <| List.concat [ buttonClasses, classes ]) + , onClick handleClick + ] + [ text label ] + + +textField : + { placeholderText : String + , handleInput : String -> msg + , classes : List String + } + -> Html msg +textField { placeholderText, handleInput, classes } = + let + inputClasses = + [ "w-full" + , "py-10" + , "lg:py-6" + , "px-16" + , "lg:px-10" + , "border" + , "rounded-lg" + , Responsive.h2 + ] + in + input + [ class (Tailwind.use <| List.concat [ inputClasses, classes ]) + , onInput handleInput + , placeholder placeholderText + ] + [] + + +overlayButton : + { label : String + , handleClick : msg + , isVisible : Bool + } + -> Html msg +overlayButton { label, handleClick, isVisible } = + let + classes = + [ "fixed" + , "top-0" + , "left-0" + , "block" + , "z-40" + , "w-screen" + , "h-screen" + , Tailwind.if_ isVisible "opacity-100" "opacity-0" + ] + in + button + [ classes |> Tailwind.use |> class + , style "background-color" "rgba(0,0,0,1.0)" + , onClick handleClick + ] + [ h1 + [ style "-webkit-text-stroke-width" "2px" + , style "-webkit-text-stroke-color" "black" + , class <| + Tailwind.use + [ "transform" + , "-rotate-90" + , "text-white" + , "font-mono" + , Responsive.h1 + ] + ] + [ text label ] + ] diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/.envrc b/users/wpcarro/website/sandbox/learnpianochords/src/server/.envrc new file mode 100644 index 000000000000..db08eac38e8e --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/.envrc @@ -0,0 +1,6 @@ +source_up +use_nix +export SERVER_PORT=3000 +export CLIENT_PORT=8000 +export GOOGLE_CLIENT_ID="$(jq -j '.google | .clientId' < ~/briefcase/secrets.json)" +export STRIPE_API_KEY="$(jq -j '.stripe | .apiKey' < ~/briefcase/secrets.json)" diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/.ghci b/users/wpcarro/website/sandbox/learnpianochords/src/server/.ghci new file mode 100644 index 000000000000..151d070ca1a4 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/.ghci @@ -0,0 +1,7 @@ +:set prompt "> " +:set -Wall + +:set -XOverloadedStrings +:set -XNoImplicitPrelude +:set -XRecordWildCards +:set -XTypeApplications diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/API.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/API.hs new file mode 100644 index 000000000000..fe3671e7aa3e --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/API.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +-------------------------------------------------------------------------------- +module API where +-------------------------------------------------------------------------------- +import Servant.API + +import qualified Types as T +-------------------------------------------------------------------------------- + +type API = "verify" + :> ReqBody '[JSON] T.VerifyGoogleSignInRequest + :> Post '[JSON] NoContent + :<|> "create-payment-intent" + :> ReqBody '[JSON] T.PaymentIntent + :> Post '[JSON] T.CreatePaymentIntentResponse diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/App.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/App.hs new file mode 100644 index 000000000000..e23757b01544 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/App.hs @@ -0,0 +1,57 @@ +-------------------------------------------------------------------------------- +module App where +-------------------------------------------------------------------------------- +import RIO hiding (Handler) +import Servant +import API +import Data.String.Conversions (cs) +import Control.Monad.IO.Class (liftIO) +import Network.Wai.Middleware.Cors +import GoogleSignIn (EncodedJWT(..), ValidationResult(..)) +import Utils + +import qualified Network.Wai.Handler.Warp as Warp +import qualified GoogleSignIn +import qualified Stripe +import qualified Types as T +-------------------------------------------------------------------------------- + +server :: T.Context -> Server API +server ctx@T.Context{..} = verifyGoogleSignIn + :<|> createPaymentIntent + where + verifyGoogleSignIn :: T.VerifyGoogleSignInRequest -> Handler NoContent + verifyGoogleSignIn T.VerifyGoogleSignInRequest{..} = do + validationResult <- liftIO $ GoogleSignIn.validateJWT False (EncodedJWT idToken) + case validationResult of + Valid _ -> do + -- If GoogleLinkedAccounts has email from JWT: + -- create a new session for email + -- Else: + -- Redirect the SPA to the sign-up / payment page + pure NoContent + err -> do + throwError err401 { errBody = err |> GoogleSignIn.explainResult |> cs } + + createPaymentIntent :: T.PaymentIntent -> Handler T.CreatePaymentIntentResponse + createPaymentIntent pmt = do + clientSecret <- liftIO $ Stripe.createPaymentIntent ctx pmt + pure T.CreatePaymentIntentResponse{..} + +run :: T.App +run = do + ctx@T.Context{..} <- ask + ctx + |> server + |> serve (Proxy @ API) + |> cors (const $ Just corsPolicy) + |> Warp.run contextServerPort + |> liftIO + pure $ Right () + where + corsPolicy :: CorsResourcePolicy + corsPolicy = simpleCorsResourcePolicy + { corsOrigins = Just (["http://localhost:8000"], True) + , corsMethods = simpleMethods ++ ["PUT", "PATCH", "DELETE", "OPTIONS"] + , corsRequestHeaders = simpleHeaders ++ ["Content-Type", "Authorization"] + } diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Fixtures.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Fixtures.hs new file mode 100644 index 000000000000..7c153e422822 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Fixtures.hs @@ -0,0 +1,67 @@ +-------------------------------------------------------------------------------- +module Fixtures where +-------------------------------------------------------------------------------- +import RIO +import Web.JWT +import Utils + +import qualified Data.Map as Map +import qualified GoogleSignIn +import qualified TestUtils +import qualified Data.Time.Clock.POSIX as POSIX +import qualified System.IO.Unsafe as Unsafe +-------------------------------------------------------------------------------- + +-- | These are the JWT fields that I'd like to overwrite in the `googleJWT` +-- function. +data JWTFields = JWTFields + { overwriteSigner :: Signer + , overwriteAuds :: [StringOrURI] + , overwriteIss :: StringOrURI + , overwriteExp :: NumericDate + } + +defaultJWTFields :: JWTFields +defaultJWTFields = do + let tenDaysFromToday = POSIX.getPOSIXTime + |> Unsafe.unsafePerformIO + |> (\x -> x * 60 * 60 * 25 * 10) + |> numericDate + |> TestUtils.unsafeJust + JWTFields + { overwriteSigner = hmacSecret "secret" + , overwriteAuds = ["771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"] + |> fmap TestUtils.unsafeStringOrURI + , overwriteIss = TestUtils.unsafeStringOrURI "accounts.google.com" + , overwriteExp = tenDaysFromToday + } + +googleJWT :: JWTFields -> GoogleSignIn.EncodedJWT +googleJWT JWTFields{..} = + encodeSigned signer jwtHeader claimSet + |> GoogleSignIn.EncodedJWT + where + signer :: Signer + signer = overwriteSigner + + jwtHeader :: JOSEHeader + jwtHeader = JOSEHeader + { typ = Just "JWT" + , cty = Nothing + , alg = Just RS256 + , kid = Just "f05415b13acb9590f70df862765c655f5a7a019e" + } + + claimSet :: JWTClaimsSet + claimSet = JWTClaimsSet + { iss = Just overwriteIss + , sub = stringOrURI "114079822315085727057" + , aud = overwriteAuds |> Right |> Just + -- TODO: Replace date creation with a human-readable date constructor. + , Web.JWT.exp = Just overwriteExp + , nbf = Nothing + -- TODO: Replace date creation with a human-readable date constructor. + , iat = numericDate 1596752853 + , unregisteredClaims = ClaimsMap (Map.fromList []) + , jti = stringOrURI "0d3d7fa1fe05bedec0a91c88294936b2b4d1b13c" + } diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs new file mode 100644 index 000000000000..dcccadcb7022 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs @@ -0,0 +1,111 @@ +-------------------------------------------------------------------------------- +module GoogleSignIn where +-------------------------------------------------------------------------------- +import RIO +import Data.String.Conversions (cs) +import Web.JWT +import Utils + +import qualified Network.HTTP.Simple as HTTP +import qualified Data.Text as Text +import qualified Web.JWT as JWT +import qualified Data.Time.Clock.POSIX as POSIX +-------------------------------------------------------------------------------- + +newtype EncodedJWT = EncodedJWT Text + deriving (Show) + +newtype DecodedJWT = DecodedJWT (JWT UnverifiedJWT) + deriving (Show) + +instance Eq DecodedJWT where + (DecodedJWT _) == (DecodedJWT _) = True + +data ValidationResult + = Valid DecodedJWT + | CannotDecodeJWT + | GoogleSaysInvalid Text + | NoMatchingClientIDs [StringOrURI] + | WrongIssuer StringOrURI + | StringOrURIParseFailure Text + | TimeConversionFailure + | MissingRequiredClaim Text + | StaleExpiry NumericDate + deriving (Eq, Show) + +-- | Returns True when the supplied `jwt` meets the following criteria: +-- * The token has been signed by Google +-- * The value of `aud` matches my Google client's ID +-- * The value of `iss` matches is "accounts.google.com" or +-- "https://accounts.google.com" +-- * The `exp` time has not passed +-- +-- Set `skipHTTP` to `True` to avoid making the network request for testing. +validateJWT :: Bool + -> EncodedJWT + -> IO ValidationResult +validateJWT skipHTTP (EncodedJWT encodedJWT) = do + case encodedJWT |> decode of + Nothing -> pure CannotDecodeJWT + Just jwt -> do + if skipHTTP then + continue jwt + else do + let request = "https://oauth2.googleapis.com/tokeninfo" + |> HTTP.setRequestQueryString [ ( "id_token", Just (cs encodedJWT) ) ] + res <- HTTP.httpLBS request + if HTTP.getResponseStatusCode res /= 200 then + pure $ GoogleSaysInvalid (res |> HTTP.getResponseBody |> cs) + else + continue jwt + where + continue :: JWT UnverifiedJWT -> IO ValidationResult + continue jwt = do + let audValues :: [StringOrURI] + audValues = jwt |> claims |> auds + expectedClientID :: Text + expectedClientID = "771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com" + expectedIssuers :: [Text] + expectedIssuers = [ "accounts.google.com" + , "https://accounts.google.com" + ] + mExpectedClientID :: Maybe StringOrURI + mExpectedClientID = stringOrURI expectedClientID + mExpectedIssuers :: Maybe [StringOrURI] + mExpectedIssuers = expectedIssuers |> traverse stringOrURI + case (mExpectedClientID, mExpectedIssuers) of + (Nothing, _) -> pure $ StringOrURIParseFailure expectedClientID + (_, Nothing) -> pure $ StringOrURIParseFailure (Text.unwords expectedIssuers) + (Just clientID, Just parsedIssuers) -> + -- TODO: Prefer reading clientID from a config. I'm thinking of the + -- AppContext type having my Configuration + if not $ clientID `elem` audValues then + pure $ NoMatchingClientIDs audValues + else + case (jwt |> claims |> iss, jwt |> claims |> JWT.exp) of + (Nothing, _) -> pure $ MissingRequiredClaim "iss" + (_, Nothing) -> pure $ MissingRequiredClaim "exp" + (Just jwtIssuer, Just jwtExpiry) -> + if not $ jwtIssuer `elem` parsedIssuers then + pure $ WrongIssuer jwtIssuer + else do + mCurrentTime <- POSIX.getPOSIXTime |> fmap numericDate + case mCurrentTime of + Nothing -> pure TimeConversionFailure + Just currentTime -> + if not $ currentTime <= jwtExpiry then + pure $ StaleExpiry jwtExpiry + else + pure $ jwt |> DecodedJWT |> Valid + +-- | Attempt to explain the `ValidationResult` to a human. +explainResult :: ValidationResult -> String +explainResult (Valid _) = "Everything appears to be valid" +explainResult CannotDecodeJWT = "We had difficulty decoding the provided JWT" +explainResult (GoogleSaysInvalid x) = "After checking with Google, they claimed that the provided JWT was invalid: " ++ cs x +explainResult (NoMatchingClientIDs audFields) = "None of the values in the `aud` field on the provided JWT match our client ID: " ++ show audFields +explainResult (WrongIssuer issuer) = "The `iss` field in the provided JWT does not match what we expect: " ++ show issuer +explainResult (StringOrURIParseFailure x) = "We had difficulty parsing values as URIs" ++ show x +explainResult TimeConversionFailure = "We had difficulty converting the current time to a value we can use to compare with the JWT's `exp` field" +explainResult (MissingRequiredClaim claim) = "Your JWT is missing the following claim: " ++ cs claim +explainResult (StaleExpiry x) = "The `exp` field on your JWT has expired" ++ x |> show |> cs diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Main.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Main.hs new file mode 100644 index 000000000000..228c3363bc59 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Main.hs @@ -0,0 +1,37 @@ +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import RIO +import Prelude (putStr, putStrLn) + +import qualified Types as T +import qualified System.Envy as Envy +import qualified App +-------------------------------------------------------------------------------- + +-- | Attempt to read environment variables from the system and initialize the +-- Context data type for our application. +getAppContext :: IO (Either String T.Context) +getAppContext = do + mEnv <- Envy.decodeEnv + case mEnv of + Left err -> pure $ Left err + Right T.Env{..} -> pure $ Right T.Context + { contextGoogleClientID = envGoogleClientID + , contextStripeAPIKey = envStripeAPIKey + , contextServerPort = envServerPort + , contextClientPort = envClientPort + } + +main :: IO () +main = do + mContext <- getAppContext + case mContext of + Left err -> putStrLn err + Right ctx -> do + result <- runRIO ctx App.run + case result of + Left err -> do + putStr "Something went wrong when executing the application: " + putStrLn $ show err + Right _ -> putStrLn "The application successfully executed." diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Spec.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Spec.hs new file mode 100644 index 000000000000..3c476bbf7b87 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Spec.hs @@ -0,0 +1,74 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import RIO +import Test.Hspec +import Utils +import Web.JWT (numericDate, decode) +import GoogleSignIn (EncodedJWT(..), DecodedJWT(..), ValidationResult(..)) + +import qualified GoogleSignIn +import qualified Fixtures as F +import qualified TestUtils +import qualified Data.Time.Clock.POSIX as POSIX +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "GoogleSignIn" $ + describe "validateJWT" $ do + let validateJWT' = GoogleSignIn.validateJWT True + it "returns a decode error when an incorrectly encoded JWT is used" $ do + validateJWT' (GoogleSignIn.EncodedJWT "rubbish") `shouldReturn` CannotDecodeJWT + + it "returns validation error when the aud field doesn't match my client ID" $ do + let auds = ["wrong-client-id"] + |> fmap TestUtils.unsafeStringOrURI + encodedJWT = F.defaultJWTFields { F.overwriteAuds = auds } + |> F.googleJWT + validateJWT' encodedJWT `shouldReturn` NoMatchingClientIDs auds + + it "returns validation success when one of the aud fields matches my client ID" $ do + let auds = ["wrong-client-id", "771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"] + |> fmap TestUtils.unsafeStringOrURI + encodedJWT@(EncodedJWT jwt) = + F.defaultJWTFields { F.overwriteAuds = auds } + |> F.googleJWT + decodedJWT = jwt |> decode |> TestUtils.unsafeJust |> DecodedJWT + validateJWT' encodedJWT `shouldReturn` Valid decodedJWT + + it "returns validation error when one of the iss field doesn't match accounts.google.com or https://accounts.google.com" $ do + let erroneousIssuer = TestUtils.unsafeStringOrURI "not-accounts.google.com" + encodedJWT = F.defaultJWTFields { F.overwriteIss = erroneousIssuer } + |> F.googleJWT + validateJWT' encodedJWT `shouldReturn` WrongIssuer erroneousIssuer + + it "returns validation success when the iss field matches accounts.google.com or https://accounts.google.com" $ do + let erroneousIssuer = TestUtils.unsafeStringOrURI "https://accounts.google.com" + encodedJWT@(EncodedJWT jwt) = + F.defaultJWTFields { F.overwriteIss = erroneousIssuer } + |> F.googleJWT + decodedJWT = jwt |> decode |> TestUtils.unsafeJust |> DecodedJWT + validateJWT' encodedJWT `shouldReturn` Valid decodedJWT + + it "fails validation when the exp field has expired" $ do + let mErroneousExp = numericDate 0 + case mErroneousExp of + Nothing -> True `shouldBe` False + Just erroneousExp -> do + let encodedJWT = F.defaultJWTFields { F.overwriteExp = erroneousExp } + |> F.googleJWT + validateJWT' encodedJWT `shouldReturn` StaleExpiry erroneousExp + + it "passes validation when the exp field is current" $ do + mFreshExp <- POSIX.getPOSIXTime + |> fmap (\x -> x * 60 * 60 * 24 * 10) -- 10 days later + |> fmap numericDate + case mFreshExp of + Nothing -> True `shouldBe` False + Just freshExp -> do + let encodedJWT@(EncodedJWT jwt) = + F.defaultJWTFields { F.overwriteExp = freshExp } + |> F.googleJWT + decodedJWT = jwt |> decode |> TestUtils.unsafeJust |> DecodedJWT + validateJWT' encodedJWT `shouldReturn` Valid decodedJWT diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Stripe.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Stripe.hs new file mode 100644 index 000000000000..5370b90abebf --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Stripe.hs @@ -0,0 +1,29 @@ +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE DataKinds #-} +-------------------------------------------------------------------------------- +module Stripe where +-------------------------------------------------------------------------------- +import RIO +import Prelude (print) +import Data.String.Conversions (cs) +import Data.Aeson +import Network.HTTP.Req + +import qualified Types as T +-------------------------------------------------------------------------------- + +endpoint :: Text -> Url 'Https +endpoint slug = + https "api.stripe.com" /: "v1" /: slug + +post :: (FromJSON b) => Text -> Text -> T.PaymentIntent -> IO (JsonResponse b) +post apiKey slug T.PaymentIntent{..} = runReq defaultHttpConfig $ do + let params = "amount" =: paymentIntentAmount + <> "currency" =: paymentIntentCurrency + req POST (endpoint slug) (ReqBodyUrlEnc params) jsonResponse (oAuth2Bearer (cs apiKey)) + +createPaymentIntent :: T.Context -> T.PaymentIntent -> IO T.Secret +createPaymentIntent T.Context{..} pmtIntent = do + res <- post contextStripeAPIKey "payment_intents" pmtIntent + let T.StripePaymentIntent{..} = responseBody res :: T.StripePaymentIntent + pure pmtIntentClientSecret diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/TestUtils.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/TestUtils.hs new file mode 100644 index 000000000000..24054bf47afd --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/TestUtils.hs @@ -0,0 +1,17 @@ +-------------------------------------------------------------------------------- +module TestUtils where +-------------------------------------------------------------------------------- +import RIO +import Web.JWT +import Data.String.Conversions (cs) +-------------------------------------------------------------------------------- + +unsafeStringOrURI :: String -> StringOrURI +unsafeStringOrURI x = + case stringOrURI (cs x) of + Nothing -> error $ "Failed to convert to StringOrURI: " ++ x + Just res -> res + +unsafeJust :: Maybe a -> a +unsafeJust Nothing = error "Attempted to force a Nothing to be a something" +unsafeJust (Just x) = x diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Types.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Types.hs new file mode 100644 index 000000000000..4a72865153ab --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Types.hs @@ -0,0 +1,146 @@ +--------------------------------------------------------------------------------G +module Types where +-------------------------------------------------------------------------------- +import RIO +import Data.Aeson +import Network.HTTP.Req +import Web.Internal.HttpApiData (ToHttpApiData(..)) +import System.Envy (FromEnv, fromEnv, env) +-------------------------------------------------------------------------------- + +-- | Read from .envrc +data Env = Env + { envGoogleClientID :: !Text + , envServerPort :: !Int + , envClientPort :: !Int + , envStripeAPIKey :: !Text + } deriving (Eq, Show) + +instance FromEnv Env where + fromEnv _ = do + envGoogleClientID <- env "GOOGLE_CLIENT_ID" + envStripeAPIKey <- env "STRIPE_API_KEY" + envServerPort <- env "SERVER_PORT" + envClientPort <- env "CLIENT_PORT" + pure Env {..} + +-- | Application context: a combination of Env and additional values. +data Context = Context + { contextGoogleClientID :: !Text + , contextStripeAPIKey :: !Text + , contextServerPort :: !Int + , contextClientPort :: !Int + } + +-- | Top-level except for our application, as RIO recommends defining. +type Failure = () + +-- | When our app executes along the "happy path" this is the type of result it +-- produces. +type Success = () + +-- | This is our application monad. +type AppM = RIO Context + +-- | The concrete type of our application. +type App = AppM (Either Failure Success) + +data VerifyGoogleSignInRequest = VerifyGoogleSignInRequest + { idToken :: !Text + } deriving (Eq, Show) + +instance FromJSON VerifyGoogleSignInRequest where + parseJSON = withObject "VerifyGoogleSignInRequest" $ \x -> do + idToken <- x .: "idToken" + pure VerifyGoogleSignInRequest{..} + +data GoogleLinkedAccount = GoogleLinkedAccount + { + -- { googleLinkedAccountUUID :: UUID + -- , googleLinkedAccountEmail :: Email + -- , googleLinkedAccountTsCreated :: Timestamp + googleLinkedAccountGivenName :: !(Maybe Text) + , googleLinkedAccountFamilyName :: !(Maybe Text) + , googleLinkedAccountFullName :: !(Maybe Text) + -- , googleLinkedAccountPictureURL :: URL + -- , googleLinkedAccountLocale :: Maybe Locale + } deriving (Eq, Show) + +data PayingCustomer = PayingCustomer + { + -- { payingCustomerAccountUUID :: UUID + -- , payingCustomerTsCreated :: Timestamp + } deriving (Eq, Show) + +data Session = Session + { + -- { sessionUUID :: UUID + -- , sessionAccountUUID :: UUID + -- , sessionTsCreated :: Timestamp + } deriving (Eq, Show) + +data CurrencyCode = USD + deriving (Eq, Show) + +instance ToJSON CurrencyCode where + toJSON USD = String "usd" + +instance FromJSON CurrencyCode where + parseJSON = withText "CurrencyCode" $ \x -> + case x of + "usd" -> pure USD + _ -> fail "Expected a valid currency code like: \"usd\"" + +instance ToHttpApiData CurrencyCode where + toQueryParam USD = "usd" + +data PaymentIntent = PaymentIntent + { paymentIntentAmount :: !Int + , paymentIntentCurrency :: !CurrencyCode + } deriving (Eq, Show) + +instance ToJSON PaymentIntent where + toJSON PaymentIntent{..} = + object [ "amount" .= paymentIntentAmount + , "currency" .= paymentIntentCurrency + ] + +instance FromJSON PaymentIntent where + parseJSON = withObject "" $ \x -> do + paymentIntentAmount <- x .: "amount" + paymentIntentCurrency <- x .: "currency" + pure PaymentIntent{..} + +instance QueryParam PaymentIntent where + queryParam = undefined + +-- All applications have their secrets... Using the secret type ensures that no +-- sensitive information will get printed to the screen. +newtype Secret = Secret Text deriving (Eq) + +instance Show Secret where + show (Secret _) = "[REDACTED]" + +instance ToJSON Secret where + toJSON (Secret x) = toJSON x + +instance FromJSON Secret where + parseJSON = withText "Secret" $ \x -> pure $ Secret x + +data CreatePaymentIntentResponse = CreatePaymentIntentResponse + { clientSecret :: Secret + } deriving (Eq, Show) + +instance ToJSON CreatePaymentIntentResponse where + toJSON CreatePaymentIntentResponse{..} = + object [ "clientSecret" .= clientSecret + ] + +data StripePaymentIntent = StripePaymentIntent + { pmtIntentClientSecret :: Secret + } deriving (Eq, Show) + +instance FromJSON StripePaymentIntent where + parseJSON = withObject "StripeCreatePaymentIntentResponse" $ \x -> do + pmtIntentClientSecret <- x .: "client_secret" + pure StripePaymentIntent{..} diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/Utils.hs b/users/wpcarro/website/sandbox/learnpianochords/src/server/Utils.hs new file mode 100644 index 000000000000..2f401af2fb8f --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/Utils.hs @@ -0,0 +1,8 @@ +-------------------------------------------------------------------------------- +module Utils where +-------------------------------------------------------------------------------- +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +(|>) :: a -> (a -> b) -> b +(|>) = (&) diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/default.nix b/users/wpcarro/website/sandbox/learnpianochords/src/server/default.nix new file mode 100644 index 000000000000..87de69cbd627 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/default.nix @@ -0,0 +1,28 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.program { + name = "server"; + srcs = builtins.path { + path = ./.; + name = "LearnPianoChords-server-src"; + }; + ghcExtensions = [ + "OverloadedStrings" + "NoImplicitPrelude" + "RecordWildCards" + "TypeApplications" + ]; + deps = hpkgs: with hpkgs; [ + servant-server + aeson + wai-cors + warp + jwt + unordered-containers + base64 + http-conduit + rio + envy + req + ]; +} diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/index.html b/users/wpcarro/website/sandbox/learnpianochords/src/server/index.html new file mode 100644 index 000000000000..459a5c8c8250 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/index.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Google Sign-in</title> + <script src="https://apis.google.com/js/platform.js" async defer></script> + <meta name="google-signin-client_id" content="771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"> + </head> + <body> + <div class="g-signin2" data-onsuccess="onSignIn"></div> + <a href="#" onclick="signOut();">Sign out</a> + <script> + function onSignIn(googleUser) { + var idToken = googleUser.getAuthResponse().id_token; + fetch('http://localhost:3000/verify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + idToken: idToken, + }) + }) + .then(x => console.log(x)) + .catch(err => console.error(err)); + } + function signOut() { + var auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(function () { + console.log('User signed out.'); + }); + } + </script> + </body> +</html> diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/init.sql b/users/wpcarro/website/sandbox/learnpianochords/src/server/init.sql new file mode 100644 index 000000000000..c220bd440636 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/init.sql @@ -0,0 +1,41 @@ +BEGIN TRANSACTION; + +DROP TABLE IF EXISTS GoogleLinkedAccounts; +DROP TABLE IF EXISTS PayingCustomers; +DROP TABLE IF EXISTS Sessions; + +-- Store some of the information that Google provides to us from the JWT. +CREATE TABLE GoogleLinkedAccounts ( + accountUUID TEXT CHECK(LENGTH(uuid) == 36) NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + tsCreated TEXT NOT NULL, -- 'YYYY-MM-DD HH:MM:SS' + givenName TEXT, + familyName TEXT, + fullName TEXT, + pictureURL TEXT, + locale TEXT, + PRIMARY KEY (accountUUID) +); + +-- Track which of our customers have a paid account. +-- Defines a one-to-one relationship between: +-- GoogleLinkedAccounts and PayingCustomers +CREATE TABLE PayingCustomers ( + accountUUID TEXT, + tsCreated TEXT, + PRIMARY KEY (accountUUID), + FOREIGN KEY (accountUUID) REFERENCES GoogleLinkedAccounts ON DELETE CASCADE +); + +-- Define mobile and web sessions for our users. +-- Defines a one-to-many relationship between: +-- GoogleLinkedAccounts and Sessions +CREATE TABLE Sessions ( + sessionUUID TEXT CHECK(LENGTH(sessionUUID) == 36) NOT NULL UNIQUE, + accountUUID TEXT, + tsCreated TEXT NOT NULL, -- 'YYYY-MM-DD HH:MM:SS' + PRIMARY KEY (sessionUUID) + FOREIGN KEY(accountUUID) REFERENCES GoogleLinkedAccounts ON DELETE CASCADE +); + +COMMIT; diff --git a/users/wpcarro/website/sandbox/learnpianochords/src/server/shell.nix b/users/wpcarro/website/sandbox/learnpianochords/src/server/shell.nix new file mode 100644 index 000000000000..ab470841e6c1 --- /dev/null +++ b/users/wpcarro/website/sandbox/learnpianochords/src/server/shell.nix @@ -0,0 +1,18 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + hspec + servant-server + aeson + wai-cors + warp + jwt + unordered-containers + base64 + http-conduit + rio + envy + req + ]; +} diff --git a/users/wpcarro/website/sandbox/typo-po/README.md b/users/wpcarro/website/sandbox/typo-po/README.md new file mode 100644 index 000000000000..9efe53eccdbb --- /dev/null +++ b/users/wpcarro/website/sandbox/typo-po/README.md @@ -0,0 +1,10 @@ +# Typo-po + +Have you ever published a blog post with typos? Or perhaps you've shared a blog +post draft with a group of friends to solicit their feedback. If anyone reads +your blog post and finds places where they can correct your typos or suggest +grammatical improvements they can use typo-po. + +## What's with the name? + +We police your typos. We prefer po-po to police though. Send us donuts. diff --git a/users/wpcarro/zoo/.envrc b/users/wpcarro/zoo/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/zoo/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/zoo/.ghci b/users/wpcarro/zoo/.ghci new file mode 100644 index 000000000000..fcae90c2987d --- /dev/null +++ b/users/wpcarro/zoo/.ghci @@ -0,0 +1,5 @@ +:set prompt "> " +:set -Wall +:set -XOverloadedStrings +:set -XRecordWildCards +:set -XTypeApplications diff --git a/users/wpcarro/zoo/Main.hs b/users/wpcarro/zoo/Main.hs new file mode 100644 index 000000000000..407d29e61e3a --- /dev/null +++ b/users/wpcarro/zoo/Main.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import RIO hiding (Handler) +import RIO.Text +import RIO.Time +import Servant +import Data.Time.Clock.POSIX +import Prelude (read) +import Text.ParserCombinators.ReadP + +import qualified Network.Wai.Handler.Warp as Warp +-------------------------------------------------------------------------------- + +type Api = "run" + :> QueryParam' '[Required] "offset" Text + :> Get '[JSON] UTCTime + :<|> "hello" + :> QueryParam "name" Text + :> Get '[JSON] Text + +server :: Server Api +server = compute :<|> hello + where + compute :: Text -> Handler UTCTime + compute x = do + case parseInput x of + Nothing -> throwError err401 + Just req -> do + res <- liftIO $ shiftTime req + pure res + hello :: Maybe Text -> Handler Text + hello mName = + case mName of + Nothing -> pure "Hello, world!" + Just name -> pure $ RIO.Text.concat ["Hello, ", name] + +data ShiftTimeRequest = ShiftTimeRequest + { shiftSeconds :: Int + , shiftMinutes :: Int + , shiftHours :: Int + , shiftDays :: Int + , shiftWeeks :: Int + , shiftMonths :: Int + , shiftQuarters :: Int + , shiftYears :: Int + } deriving (Eq, Show) + +instance Semigroup ShiftTimeRequest where + (ShiftTimeRequest as am ah ad aw amonths aq ay) <> (ShiftTimeRequest bs bm bh bd bw bmonths bq by) = + ShiftTimeRequest + { shiftSeconds = as + bs + , shiftMinutes = am + bm + , shiftHours = ah + bh + , shiftDays = ad + bd + , shiftWeeks = aw + bw + , shiftMonths = amonths + bmonths + , shiftQuarters = aq + bq + , shiftYears = ay + by + } + +instance Monoid ShiftTimeRequest where + mempty = defaultShiftTimeRequest + +defaultShiftTimeRequest :: ShiftTimeRequest +defaultShiftTimeRequest = ShiftTimeRequest + { shiftSeconds = 0 + , shiftMinutes = 0 + , shiftHours = 0 + , shiftDays = 0 + , shiftWeeks = 0 + , shiftMonths = 0 + , shiftQuarters = 0 + , shiftYears = 0 + } + +-- This basically broken because it doesn't account for: +-- Exhales... time stuff +-- - Leap seconds, leap days, leap years... +-- - Months like February having 28 days and others having 31 +-- - other things that I'm probably not considering +toSeconds :: ShiftTimeRequest -> NominalDiffTime +toSeconds ShiftTimeRequest{..} = do + let minutes = 60 + hours = minutes * 60 + days = hours * 24 + weeks = days * 7 + months = weeks * 4 + quarters = months * 3 + years = days * 365 + fromIntegral $ shiftSeconds + + shiftMinutes * minutes + + shiftHours * hours + + shiftDays * days + + shiftWeeks * weeks + + shiftMonths * months + + shiftQuarters * quarters + + shiftYears * years + +shiftTime :: ShiftTimeRequest -> IO UTCTime +shiftTime req = do + t <- getPOSIXTime + let t' = t + toSeconds req + pure $ posixSecondsToUTCTime t' + +data Unit = Second + | Minute + | Hour + | Day + | Week + | Month + | Quarter + | Year + deriving (Eq, Show) + +digit :: ReadP Char +digit = + satisfy (\c -> c >= '0' && c <= '9') + +unit :: ReadP Unit +unit = do + c <- get + case c of + 's' -> pure Second + 'm' -> pure Minute + 'h' -> pure Hour + 'd' -> pure Day + 'w' -> pure Week + 'M' -> pure Month + 'q' -> pure Quarter + 'y' -> pure Year + _ -> fail $ "We don't support this unit: " ++ show c + +request :: ReadP ShiftTimeRequest +request = do + negative <- option Nothing $ fmap Just (satisfy (== '-')) + n <- read <$> many1 digit + u <- unit + let amt = if isJust negative then -1 * n else n + case u of + Second -> pure $ defaultShiftTimeRequest { shiftSeconds = amt } + Minute -> pure $ defaultShiftTimeRequest { shiftMinutes = amt } + Hour -> pure $ defaultShiftTimeRequest { shiftHours = amt } + Day -> pure $ defaultShiftTimeRequest { shiftDays = amt } + Week -> pure $ defaultShiftTimeRequest { shiftWeeks = amt } + Month -> pure $ defaultShiftTimeRequest { shiftMonths = amt } + Quarter -> pure $ defaultShiftTimeRequest { shiftQuarters = amt } + Year -> pure $ defaultShiftTimeRequest { shiftYears = amt } + +parseInput :: Text -> Maybe ShiftTimeRequest +parseInput x = + case readP_to_S (manyTill request eof) (unpack x) of + [(xs, "")] -> Just $ mconcat xs + _ -> Nothing + +main :: IO () +main = Warp.run 8000 $ serve (Proxy @ Api) server diff --git a/users/wpcarro/zoo/Spec.hs b/users/wpcarro/zoo/Spec.hs new file mode 100644 index 000000000000..ba3f71d7c754 --- /dev/null +++ b/users/wpcarro/zoo/Spec.hs @@ -0,0 +1,54 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import RIO +import Test.Hspec +import Test.QuickCheck +import Main hiding (main) + +import qualified RIO.Text as Text +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "Main" $ do + it "handles seconds" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "s"]) == + (Just defaultShiftTimeRequest { shiftSeconds = x }) + + it "handles minutes" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "m"]) == + (Just defaultShiftTimeRequest { shiftMinutes = x }) + + it "handles hours" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "h"]) == + (Just defaultShiftTimeRequest { shiftHours = x }) + + it "handles days" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "d"]) == + (Just defaultShiftTimeRequest { shiftDays = x }) + + it "handles weeks" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "w"]) == + (Just defaultShiftTimeRequest { shiftWeeks = x }) + + it "handles months" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "M"]) == + (Just defaultShiftTimeRequest { shiftMonths = x }) + + it "handles quarters" $ do + property $ \x -> parseTime (Text.concat [x & show & Text.pack, "q"]) == + (Just defaultShiftTimeRequest { shiftQuarters = x }) + + it "handles multiple shifts" $ do + parseTime "1s-20m5h0d-4w100M-3y2q" == + (Just $ ShiftTimeRequest + { shiftSeconds = 1 + , shiftMinutes = -20 + , shiftHours = 5 + , shiftDays = 0 + , shiftWeeks = -4 + , shiftMonths = 100 + , shiftQuarters = 2 + , shiftYears = -3 + }) diff --git a/users/wpcarro/zoo/default.nix b/users/wpcarro/zoo/default.nix new file mode 100644 index 000000000000..35de24d9c2cc --- /dev/null +++ b/users/wpcarro/zoo/default.nix @@ -0,0 +1,21 @@ +{ briefcase, ... }: + +briefcase.buildHaskell.program { + name = "zoo"; + srcs = builtins.path { + path = ./.; + name = "zoo-src"; + }; + ghcExtensions = [ + "OverloadedStrings" + "NoImplicitPrelude" + "RecordWildCards" + "TypeApplications" + ]; + deps = hpkgs: with hpkgs; [ + servant-server + aeson + warp + rio + ]; +} diff --git a/users/wpcarro/zoo/shell.nix b/users/wpcarro/zoo/shell.nix new file mode 100644 index 000000000000..944c5acc7f28 --- /dev/null +++ b/users/wpcarro/zoo/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import <briefcase> {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + servant-server + aeson + warp + rio + ]; +} |